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 com.android.internal.R; 20 21 import android.content.Context; 22 import android.content.res.Configuration; 23 import android.content.res.Resources; 24 25 import java.util.Calendar; 26 import java.util.Date; 27 import java.util.Formatter; 28 import java.util.GregorianCalendar; 29 import java.util.Locale; 30 import java.util.TimeZone; 31 32 import libcore.icu.LocaleData; 33 34 /** 35 * This class contains various date-related utilities for creating text for things like 36 * elapsed time and date ranges, strings for days of the week and months, and AM/PM text etc. 37 */ 38 public class DateUtils 39 { 40 private static final Object sLock = new Object(); 41 private static Configuration sLastConfig; 42 private static String sElapsedFormatMMSS; 43 private static String sElapsedFormatHMMSS; 44 45 public static final long SECOND_IN_MILLIS = 1000; 46 public static final long MINUTE_IN_MILLIS = SECOND_IN_MILLIS * 60; 47 public static final long HOUR_IN_MILLIS = MINUTE_IN_MILLIS * 60; 48 public static final long DAY_IN_MILLIS = HOUR_IN_MILLIS * 24; 49 public static final long WEEK_IN_MILLIS = DAY_IN_MILLIS * 7; 50 /** 51 * This constant is actually the length of 364 days, not of a year! 52 */ 53 public static final long YEAR_IN_MILLIS = WEEK_IN_MILLIS * 52; 54 55 // The following FORMAT_* symbols are used for specifying the format of 56 // dates and times in the formatDateRange method. 57 public static final int FORMAT_SHOW_TIME = 0x00001; 58 public static final int FORMAT_SHOW_WEEKDAY = 0x00002; 59 public static final int FORMAT_SHOW_YEAR = 0x00004; 60 public static final int FORMAT_NO_YEAR = 0x00008; 61 public static final int FORMAT_SHOW_DATE = 0x00010; 62 public static final int FORMAT_NO_MONTH_DAY = 0x00020; 63 @Deprecated 64 public static final int FORMAT_12HOUR = 0x00040; 65 @Deprecated 66 public static final int FORMAT_24HOUR = 0x00080; 67 @Deprecated 68 public static final int FORMAT_CAP_AMPM = 0x00100; 69 public static final int FORMAT_NO_NOON = 0x00200; 70 @Deprecated 71 public static final int FORMAT_CAP_NOON = 0x00400; 72 public static final int FORMAT_NO_MIDNIGHT = 0x00800; 73 @Deprecated 74 public static final int FORMAT_CAP_MIDNIGHT = 0x01000; 75 /** 76 * @deprecated Use 77 * {@link #formatDateRange(Context, Formatter, long, long, int, String) formatDateRange} 78 * and pass in {@link Time#TIMEZONE_UTC Time.TIMEZONE_UTC} for the timeZone instead. 79 */ 80 @Deprecated 81 public static final int FORMAT_UTC = 0x02000; 82 public static final int FORMAT_ABBREV_TIME = 0x04000; 83 public static final int FORMAT_ABBREV_WEEKDAY = 0x08000; 84 public static final int FORMAT_ABBREV_MONTH = 0x10000; 85 public static final int FORMAT_NUMERIC_DATE = 0x20000; 86 public static final int FORMAT_ABBREV_RELATIVE = 0x40000; 87 public static final int FORMAT_ABBREV_ALL = 0x80000; 88 @Deprecated 89 public static final int FORMAT_CAP_NOON_MIDNIGHT = (FORMAT_CAP_NOON | FORMAT_CAP_MIDNIGHT); 90 @Deprecated 91 public static final int FORMAT_NO_NOON_MIDNIGHT = (FORMAT_NO_NOON | FORMAT_NO_MIDNIGHT); 92 93 // Date and time format strings that are constant and don't need to be 94 // translated. 95 /** 96 * This is not actually the preferred 24-hour date format in all locales. 97 * @deprecated Use {@link java.text.SimpleDateFormat} instead. 98 */ 99 @Deprecated 100 public static final String HOUR_MINUTE_24 = "%H:%M"; 101 public static final String MONTH_FORMAT = "%B"; 102 /** 103 * This is not actually a useful month name in all locales. 104 * @deprecated Use {@link java.text.SimpleDateFormat} instead. 105 */ 106 @Deprecated 107 public static final String ABBREV_MONTH_FORMAT = "%b"; 108 public static final String NUMERIC_MONTH_FORMAT = "%m"; 109 public static final String MONTH_DAY_FORMAT = "%-d"; 110 public static final String YEAR_FORMAT = "%Y"; 111 public static final String YEAR_FORMAT_TWO_DIGITS = "%g"; 112 public static final String WEEKDAY_FORMAT = "%A"; 113 public static final String ABBREV_WEEKDAY_FORMAT = "%a"; 114 115 // This table is used to lookup the resource string id of a format string 116 // used for formatting a start and end date that fall in the same year. 117 // The index is constructed from a bit-wise OR of the boolean values: 118 // {showTime, showYear, showWeekDay}. For example, if showYear and 119 // showWeekDay are both true, then the index would be 3. 120 /** @deprecated Do not use. */ 121 public static final int sameYearTable[] = { 122 com.android.internal.R.string.same_year_md1_md2, 123 com.android.internal.R.string.same_year_wday1_md1_wday2_md2, 124 com.android.internal.R.string.same_year_mdy1_mdy2, 125 com.android.internal.R.string.same_year_wday1_mdy1_wday2_mdy2, 126 com.android.internal.R.string.same_year_md1_time1_md2_time2, 127 com.android.internal.R.string.same_year_wday1_md1_time1_wday2_md2_time2, 128 com.android.internal.R.string.same_year_mdy1_time1_mdy2_time2, 129 com.android.internal.R.string.same_year_wday1_mdy1_time1_wday2_mdy2_time2, 130 131 // Numeric date strings 132 com.android.internal.R.string.numeric_md1_md2, 133 com.android.internal.R.string.numeric_wday1_md1_wday2_md2, 134 com.android.internal.R.string.numeric_mdy1_mdy2, 135 com.android.internal.R.string.numeric_wday1_mdy1_wday2_mdy2, 136 com.android.internal.R.string.numeric_md1_time1_md2_time2, 137 com.android.internal.R.string.numeric_wday1_md1_time1_wday2_md2_time2, 138 com.android.internal.R.string.numeric_mdy1_time1_mdy2_time2, 139 com.android.internal.R.string.numeric_wday1_mdy1_time1_wday2_mdy2_time2, 140 }; 141 142 // This table is used to lookup the resource string id of a format string 143 // used for formatting a start and end date that fall in the same month. 144 // The index is constructed from a bit-wise OR of the boolean values: 145 // {showTime, showYear, showWeekDay}. For example, if showYear and 146 // showWeekDay are both true, then the index would be 3. 147 /** @deprecated Do not use. */ 148 public static final int sameMonthTable[] = { 149 com.android.internal.R.string.same_month_md1_md2, 150 com.android.internal.R.string.same_month_wday1_md1_wday2_md2, 151 com.android.internal.R.string.same_month_mdy1_mdy2, 152 com.android.internal.R.string.same_month_wday1_mdy1_wday2_mdy2, 153 com.android.internal.R.string.same_month_md1_time1_md2_time2, 154 com.android.internal.R.string.same_month_wday1_md1_time1_wday2_md2_time2, 155 com.android.internal.R.string.same_month_mdy1_time1_mdy2_time2, 156 com.android.internal.R.string.same_month_wday1_mdy1_time1_wday2_mdy2_time2, 157 158 com.android.internal.R.string.numeric_md1_md2, 159 com.android.internal.R.string.numeric_wday1_md1_wday2_md2, 160 com.android.internal.R.string.numeric_mdy1_mdy2, 161 com.android.internal.R.string.numeric_wday1_mdy1_wday2_mdy2, 162 com.android.internal.R.string.numeric_md1_time1_md2_time2, 163 com.android.internal.R.string.numeric_wday1_md1_time1_wday2_md2_time2, 164 com.android.internal.R.string.numeric_mdy1_time1_mdy2_time2, 165 com.android.internal.R.string.numeric_wday1_mdy1_time1_wday2_mdy2_time2, 166 }; 167 168 /** 169 * Request the full spelled-out name. For use with the 'abbrev' parameter of 170 * {@link #getDayOfWeekString} and {@link #getMonthString}. 171 * 172 * @more <p> 173 * e.g. "Sunday" or "January" 174 * @deprecated Use {@link java.text.SimpleDateFormat} instead. 175 */ 176 @Deprecated 177 public static final int LENGTH_LONG = 10; 178 179 /** 180 * Request an abbreviated version of the name. For use with the 'abbrev' 181 * parameter of {@link #getDayOfWeekString} and {@link #getMonthString}. 182 * 183 * @more <p> 184 * e.g. "Sun" or "Jan" 185 * @deprecated Use {@link java.text.SimpleDateFormat} instead. 186 */ 187 @Deprecated 188 public static final int LENGTH_MEDIUM = 20; 189 190 /** 191 * Request a shorter abbreviated version of the name. 192 * For use with the 'abbrev' parameter of {@link #getDayOfWeekString} and {@link #getMonthString}. 193 * @more 194 * <p>e.g. "Su" or "Jan" 195 * <p>In most languages, the results returned for LENGTH_SHORT will be the same as 196 * the results returned for {@link #LENGTH_MEDIUM}. 197 * @deprecated Use {@link java.text.SimpleDateFormat} instead. 198 */ 199 @Deprecated 200 public static final int LENGTH_SHORT = 30; 201 202 /** 203 * Request an even shorter abbreviated version of the name. 204 * Do not use this. Currently this will always return the same result 205 * as {@link #LENGTH_SHORT}. 206 * @deprecated Use {@link java.text.SimpleDateFormat} instead. 207 */ 208 @Deprecated 209 public static final int LENGTH_SHORTER = 40; 210 211 /** 212 * Request an even shorter abbreviated version of the name. 213 * For use with the 'abbrev' parameter of {@link #getDayOfWeekString} and {@link #getMonthString}. 214 * @more 215 * <p>e.g. "S", "T", "T" or "J" 216 * <p>In some languages, the results returned for LENGTH_SHORTEST will be the same as 217 * the results returned for {@link #LENGTH_SHORT}. 218 * @deprecated Use {@link java.text.SimpleDateFormat} instead. 219 */ 220 @Deprecated 221 public static final int LENGTH_SHORTEST = 50; 222 223 /** 224 * Return a string for the day of the week. 225 * @param dayOfWeek One of {@link Calendar#SUNDAY Calendar.SUNDAY}, 226 * {@link Calendar#MONDAY Calendar.MONDAY}, etc. 227 * @param abbrev One of {@link #LENGTH_LONG}, {@link #LENGTH_SHORT}, 228 * {@link #LENGTH_MEDIUM}, or {@link #LENGTH_SHORTEST}. 229 * Note that in most languages, {@link #LENGTH_SHORT} 230 * will return the same as {@link #LENGTH_MEDIUM}. 231 * Undefined lengths will return {@link #LENGTH_MEDIUM} 232 * but may return something different in the future. 233 * @throws IndexOutOfBoundsException if the dayOfWeek is out of bounds. 234 * @deprecated Use {@link java.text.SimpleDateFormat} instead. 235 */ 236 @Deprecated 237 public static String getDayOfWeekString(int dayOfWeek, int abbrev) { 238 LocaleData d = LocaleData.get(Locale.getDefault()); 239 String[] names; 240 switch (abbrev) { 241 case LENGTH_LONG: names = d.longWeekdayNames; break; 242 case LENGTH_MEDIUM: names = d.shortWeekdayNames; break; 243 case LENGTH_SHORT: names = d.shortWeekdayNames; break; // TODO 244 case LENGTH_SHORTER: names = d.shortWeekdayNames; break; // TODO 245 case LENGTH_SHORTEST: names = d.tinyWeekdayNames; break; 246 default: names = d.shortWeekdayNames; break; 247 } 248 return names[dayOfWeek]; 249 } 250 251 /** 252 * Return a localized string for AM or PM. 253 * @param ampm Either {@link Calendar#AM Calendar.AM} or {@link Calendar#PM Calendar.PM}. 254 * @throws IndexOutOfBoundsException if the ampm is out of bounds. 255 * @return Localized version of "AM" or "PM". 256 * @deprecated Use {@link java.text.SimpleDateFormat} instead. 257 */ 258 @Deprecated 259 public static String getAMPMString(int ampm) { 260 return LocaleData.get(Locale.getDefault()).amPm[ampm - Calendar.AM]; 261 } 262 263 /** 264 * Return a localized string for the month of the year. 265 * @param month One of {@link Calendar#JANUARY Calendar.JANUARY}, 266 * {@link Calendar#FEBRUARY Calendar.FEBRUARY}, etc. 267 * @param abbrev One of {@link #LENGTH_LONG}, {@link #LENGTH_MEDIUM}, 268 * or {@link #LENGTH_SHORTEST}. 269 * Undefined lengths will return {@link #LENGTH_MEDIUM} 270 * but may return something different in the future. 271 * @return Localized month of the year. 272 * @deprecated Use {@link java.text.SimpleDateFormat} instead. 273 */ 274 @Deprecated 275 public static String getMonthString(int month, int abbrev) { 276 LocaleData d = LocaleData.get(Locale.getDefault()); 277 String[] names; 278 switch (abbrev) { 279 case LENGTH_LONG: names = d.longMonthNames; break; 280 case LENGTH_MEDIUM: names = d.shortMonthNames; break; 281 case LENGTH_SHORT: names = d.shortMonthNames; break; 282 case LENGTH_SHORTER: names = d.shortMonthNames; break; 283 case LENGTH_SHORTEST: names = d.tinyMonthNames; break; 284 default: names = d.shortMonthNames; break; 285 } 286 return names[month]; 287 } 288 289 /** 290 * Returns a string describing the elapsed time since startTime. 291 * @param startTime some time in the past. 292 * @return a String object containing the elapsed time. 293 * @see #getRelativeTimeSpanString(long, long, long) 294 */ 295 public static CharSequence getRelativeTimeSpanString(long startTime) { 296 return getRelativeTimeSpanString(startTime, System.currentTimeMillis(), MINUTE_IN_MILLIS); 297 } 298 299 /** 300 * Returns a string describing 'time' as a time relative to 'now'. 301 * <p> 302 * Time spans in the past are formatted like "42 minutes ago". 303 * Time spans in the future are formatted like "in 42 minutes". 304 * 305 * @param time the time to describe, in milliseconds 306 * @param now the current time in milliseconds 307 * @param minResolution the minimum timespan to report. For example, a time 3 seconds in the 308 * past will be reported as "0 minutes ago" if this is set to MINUTE_IN_MILLIS. Pass one of 309 * 0, MINUTE_IN_MILLIS, HOUR_IN_MILLIS, DAY_IN_MILLIS, WEEK_IN_MILLIS 310 */ 311 public static CharSequence getRelativeTimeSpanString(long time, long now, long minResolution) { 312 int flags = FORMAT_SHOW_DATE | FORMAT_SHOW_YEAR | FORMAT_ABBREV_MONTH; 313 return getRelativeTimeSpanString(time, now, minResolution, flags); 314 } 315 316 /** 317 * Returns a string describing 'time' as a time relative to 'now'. 318 * <p> 319 * Time spans in the past are formatted like "42 minutes ago". Time spans in 320 * the future are formatted like "in 42 minutes". 321 * <p> 322 * Can use {@link #FORMAT_ABBREV_RELATIVE} flag to use abbreviated relative 323 * times, like "42 mins ago". 324 * 325 * @param time the time to describe, in milliseconds 326 * @param now the current time in milliseconds 327 * @param minResolution the minimum timespan to report. For example, a time 328 * 3 seconds in the past will be reported as "0 minutes ago" if 329 * this is set to MINUTE_IN_MILLIS. Pass one of 0, 330 * MINUTE_IN_MILLIS, HOUR_IN_MILLIS, DAY_IN_MILLIS, 331 * WEEK_IN_MILLIS 332 * @param flags a bit mask of formatting options, such as 333 * {@link #FORMAT_NUMERIC_DATE} or 334 * {@link #FORMAT_ABBREV_RELATIVE} 335 */ 336 public static CharSequence getRelativeTimeSpanString(long time, long now, long minResolution, 337 int flags) { 338 Resources r = Resources.getSystem(); 339 boolean abbrevRelative = (flags & (FORMAT_ABBREV_RELATIVE | FORMAT_ABBREV_ALL)) != 0; 340 341 boolean past = (now >= time); 342 long duration = Math.abs(now - time); 343 344 int resId; 345 long count; 346 if (duration < MINUTE_IN_MILLIS && minResolution < MINUTE_IN_MILLIS) { 347 count = duration / SECOND_IN_MILLIS; 348 if (past) { 349 if (abbrevRelative) { 350 resId = com.android.internal.R.plurals.abbrev_num_seconds_ago; 351 } else { 352 resId = com.android.internal.R.plurals.num_seconds_ago; 353 } 354 } else { 355 if (abbrevRelative) { 356 resId = com.android.internal.R.plurals.abbrev_in_num_seconds; 357 } else { 358 resId = com.android.internal.R.plurals.in_num_seconds; 359 } 360 } 361 } else if (duration < HOUR_IN_MILLIS && minResolution < HOUR_IN_MILLIS) { 362 count = duration / MINUTE_IN_MILLIS; 363 if (past) { 364 if (abbrevRelative) { 365 resId = com.android.internal.R.plurals.abbrev_num_minutes_ago; 366 } else { 367 resId = com.android.internal.R.plurals.num_minutes_ago; 368 } 369 } else { 370 if (abbrevRelative) { 371 resId = com.android.internal.R.plurals.abbrev_in_num_minutes; 372 } else { 373 resId = com.android.internal.R.plurals.in_num_minutes; 374 } 375 } 376 } else if (duration < DAY_IN_MILLIS && minResolution < DAY_IN_MILLIS) { 377 count = duration / HOUR_IN_MILLIS; 378 if (past) { 379 if (abbrevRelative) { 380 resId = com.android.internal.R.plurals.abbrev_num_hours_ago; 381 } else { 382 resId = com.android.internal.R.plurals.num_hours_ago; 383 } 384 } else { 385 if (abbrevRelative) { 386 resId = com.android.internal.R.plurals.abbrev_in_num_hours; 387 } else { 388 resId = com.android.internal.R.plurals.in_num_hours; 389 } 390 } 391 } else if (duration < WEEK_IN_MILLIS && minResolution < WEEK_IN_MILLIS) { 392 return getRelativeDayString(r, time, now); 393 } else { 394 // We know that we won't be showing the time, so it is safe to pass 395 // in a null context. 396 return formatDateRange(null, time, time, flags); 397 } 398 399 String format = r.getQuantityString(resId, (int) count); 400 return String.format(format, count); 401 } 402 403 /** 404 * Return string describing the elapsed time since startTime formatted like 405 * "[relative time/date], [time]". 406 * <p> 407 * Example output strings for the US date format. 408 * <ul> 409 * <li>3 mins ago, 10:15 AM</li> 410 * <li>yesterday, 12:20 PM</li> 411 * <li>Dec 12, 4:12 AM</li> 412 * <li>11/14/2007, 8:20 AM</li> 413 * </ul> 414 * 415 * @param time some time in the past. 416 * @param minResolution the minimum elapsed time (in milliseconds) to report 417 * when showing relative times. For example, a time 3 seconds in 418 * the past will be reported as "0 minutes ago" if this is set to 419 * {@link #MINUTE_IN_MILLIS}. 420 * @param transitionResolution the elapsed time (in milliseconds) at which 421 * to stop reporting relative measurements. Elapsed times greater 422 * than this resolution will default to normal date formatting. 423 * For example, will transition from "6 days ago" to "Dec 12" 424 * when using {@link #WEEK_IN_MILLIS}. 425 */ 426 public static CharSequence getRelativeDateTimeString(Context c, long time, long minResolution, 427 long transitionResolution, int flags) { 428 Resources r = Resources.getSystem(); 429 430 long now = System.currentTimeMillis(); 431 long duration = Math.abs(now - time); 432 433 // getRelativeTimeSpanString() doesn't correctly format relative dates 434 // above a week or exact dates below a day, so clamp 435 // transitionResolution as needed. 436 if (transitionResolution > WEEK_IN_MILLIS) { 437 transitionResolution = WEEK_IN_MILLIS; 438 } else if (transitionResolution < DAY_IN_MILLIS) { 439 transitionResolution = DAY_IN_MILLIS; 440 } 441 442 CharSequence timeClause = formatDateRange(c, time, time, FORMAT_SHOW_TIME); 443 444 String result; 445 if (duration < transitionResolution) { 446 CharSequence relativeClause = getRelativeTimeSpanString(time, now, minResolution, flags); 447 result = r.getString(com.android.internal.R.string.relative_time, relativeClause, timeClause); 448 } else { 449 CharSequence dateClause = getRelativeTimeSpanString(c, time, false); 450 result = r.getString(com.android.internal.R.string.date_time, dateClause, timeClause); 451 } 452 453 return result; 454 } 455 456 /** 457 * Returns a string describing a day relative to the current day. For example if the day is 458 * today this function returns "Today", if the day was a week ago it returns "7 days ago", and 459 * if the day is in 2 weeks it returns "in 14 days". 460 * 461 * @param r the resources 462 * @param day the relative day to describe in UTC milliseconds 463 * @param today the current time in UTC milliseconds 464 */ 465 private static final String getRelativeDayString(Resources r, long day, long today) { 466 Locale locale = r.getConfiguration().locale; 467 if (locale == null) { 468 locale = Locale.getDefault(); 469 } 470 471 // TODO: use TimeZone.getOffset instead. 472 Time startTime = new Time(); 473 startTime.set(day); 474 int startDay = Time.getJulianDay(day, startTime.gmtoff); 475 476 Time currentTime = new Time(); 477 currentTime.set(today); 478 int currentDay = Time.getJulianDay(today, currentTime.gmtoff); 479 480 int days = Math.abs(currentDay - startDay); 481 boolean past = (today > day); 482 483 // TODO: some locales name other days too, such as de_DE's "Vorgestern" (today - 2). 484 if (days == 1) { 485 if (past) { 486 return LocaleData.get(locale).yesterday; 487 } else { 488 return LocaleData.get(locale).tomorrow; 489 } 490 } else if (days == 0) { 491 return LocaleData.get(locale).today; 492 } 493 494 int resId; 495 if (past) { 496 resId = com.android.internal.R.plurals.num_days_ago; 497 } else { 498 resId = com.android.internal.R.plurals.in_num_days; 499 } 500 501 String format = r.getQuantityString(resId, days); 502 return String.format(format, days); 503 } 504 505 private static void initFormatStrings() { 506 synchronized (sLock) { 507 initFormatStringsLocked(); 508 } 509 } 510 511 private static void initFormatStringsLocked() { 512 Resources r = Resources.getSystem(); 513 Configuration cfg = r.getConfiguration(); 514 if (sLastConfig == null || !sLastConfig.equals(cfg)) { 515 sLastConfig = cfg; 516 sElapsedFormatMMSS = r.getString(com.android.internal.R.string.elapsed_time_short_format_mm_ss); 517 sElapsedFormatHMMSS = r.getString(com.android.internal.R.string.elapsed_time_short_format_h_mm_ss); 518 } 519 } 520 521 /** 522 * Return given duration in a human-friendly format. For example, "4 523 * minutes" or "1 second". Returns only largest meaningful unit of time, 524 * from seconds up to hours. 525 * 526 * @hide 527 */ 528 public static CharSequence formatDuration(long millis) { 529 final Resources res = Resources.getSystem(); 530 if (millis >= HOUR_IN_MILLIS) { 531 final int hours = (int) ((millis + 1800000) / HOUR_IN_MILLIS); 532 return res.getQuantityString( 533 com.android.internal.R.plurals.duration_hours, hours, hours); 534 } else if (millis >= MINUTE_IN_MILLIS) { 535 final int minutes = (int) ((millis + 30000) / MINUTE_IN_MILLIS); 536 return res.getQuantityString( 537 com.android.internal.R.plurals.duration_minutes, minutes, minutes); 538 } else { 539 final int seconds = (int) ((millis + 500) / SECOND_IN_MILLIS); 540 return res.getQuantityString( 541 com.android.internal.R.plurals.duration_seconds, seconds, seconds); 542 } 543 } 544 545 /** 546 * Formats an elapsed time in the form "MM:SS" or "H:MM:SS" 547 * for display on the call-in-progress screen. 548 * @param elapsedSeconds the elapsed time in seconds. 549 */ 550 public static String formatElapsedTime(long elapsedSeconds) { 551 return formatElapsedTime(null, elapsedSeconds); 552 } 553 554 /** 555 * Formats an elapsed time in a format like "MM:SS" or "H:MM:SS" (using a form 556 * suited to the current locale), similar to that used on the call-in-progress 557 * screen. 558 * 559 * @param recycle {@link StringBuilder} to recycle, or null to use a temporary one. 560 * @param elapsedSeconds the elapsed time in seconds. 561 */ 562 public static String formatElapsedTime(StringBuilder recycle, long elapsedSeconds) { 563 // Break the elapsed seconds into hours, minutes, and seconds. 564 long hours = 0; 565 long minutes = 0; 566 long seconds = 0; 567 if (elapsedSeconds >= 3600) { 568 hours = elapsedSeconds / 3600; 569 elapsedSeconds -= hours * 3600; 570 } 571 if (elapsedSeconds >= 60) { 572 minutes = elapsedSeconds / 60; 573 elapsedSeconds -= minutes * 60; 574 } 575 seconds = elapsedSeconds; 576 577 // Create a StringBuilder if we weren't given one to recycle. 578 // TODO: if we cared, we could have a thread-local temporary StringBuilder. 579 StringBuilder sb = recycle; 580 if (sb == null) { 581 sb = new StringBuilder(8); 582 } else { 583 sb.setLength(0); 584 } 585 586 // Format the broken-down time in a locale-appropriate way. 587 // TODO: use icu4c when http://unicode.org/cldr/trac/ticket/3407 is fixed. 588 Formatter f = new Formatter(sb, Locale.getDefault()); 589 initFormatStrings(); 590 if (hours > 0) { 591 return f.format(sElapsedFormatHMMSS, hours, minutes, seconds).toString(); 592 } else { 593 return f.format(sElapsedFormatMMSS, minutes, seconds).toString(); 594 } 595 } 596 597 /** 598 * Format a date / time such that if the then is on the same day as now, it shows 599 * just the time and if it's a different day, it shows just the date. 600 * 601 * <p>The parameters dateFormat and timeFormat should each be one of 602 * {@link java.text.DateFormat#DEFAULT}, 603 * {@link java.text.DateFormat#FULL}, 604 * {@link java.text.DateFormat#LONG}, 605 * {@link java.text.DateFormat#MEDIUM} 606 * or 607 * {@link java.text.DateFormat#SHORT} 608 * 609 * @param then the date to format 610 * @param now the base time 611 * @param dateStyle how to format the date portion. 612 * @param timeStyle how to format the time portion. 613 */ 614 public static final CharSequence formatSameDayTime(long then, long now, 615 int dateStyle, int timeStyle) { 616 Calendar thenCal = new GregorianCalendar(); 617 thenCal.setTimeInMillis(then); 618 Date thenDate = thenCal.getTime(); 619 Calendar nowCal = new GregorianCalendar(); 620 nowCal.setTimeInMillis(now); 621 622 java.text.DateFormat f; 623 624 if (thenCal.get(Calendar.YEAR) == nowCal.get(Calendar.YEAR) 625 && thenCal.get(Calendar.MONTH) == nowCal.get(Calendar.MONTH) 626 && thenCal.get(Calendar.DAY_OF_MONTH) == nowCal.get(Calendar.DAY_OF_MONTH)) { 627 f = java.text.DateFormat.getTimeInstance(timeStyle); 628 } else { 629 f = java.text.DateFormat.getDateInstance(dateStyle); 630 } 631 return f.format(thenDate); 632 } 633 634 /** 635 * @return true if the supplied when is today else false 636 */ 637 public static boolean isToday(long when) { 638 Time time = new Time(); 639 time.set(when); 640 641 int thenYear = time.year; 642 int thenMonth = time.month; 643 int thenMonthDay = time.monthDay; 644 645 time.set(System.currentTimeMillis()); 646 return (thenYear == time.year) 647 && (thenMonth == time.month) 648 && (thenMonthDay == time.monthDay); 649 } 650 651 /** 652 * Formats a date or a time range according to the local conventions. 653 * <p> 654 * Note that this is a convenience method. Using it involves creating an 655 * internal {@link java.util.Formatter} instance on-the-fly, which is 656 * somewhat costly in terms of memory and time. This is probably acceptable 657 * if you use the method only rarely, but if you rely on it for formatting a 658 * large number of dates, consider creating and reusing your own 659 * {@link java.util.Formatter} instance and use the version of 660 * {@link #formatDateRange(Context, long, long, int) formatDateRange} 661 * that takes a {@link java.util.Formatter}. 662 * 663 * @param context the context is required only if the time is shown 664 * @param startMillis the start time in UTC milliseconds 665 * @param endMillis the end time in UTC milliseconds 666 * @param flags a bit mask of options See 667 * {@link #formatDateRange(Context, Formatter, long, long, int, String) formatDateRange} 668 * @return a string containing the formatted date/time range. 669 */ 670 public static String formatDateRange(Context context, long startMillis, 671 long endMillis, int flags) { 672 Formatter f = new Formatter(new StringBuilder(50), Locale.getDefault()); 673 return formatDateRange(context, f, startMillis, endMillis, flags).toString(); 674 } 675 676 /** 677 * Formats a date or a time range according to the local conventions. 678 * <p> 679 * Note that this is a convenience method for formatting the date or 680 * time range in the local time zone. If you want to specify the time 681 * zone please use 682 * {@link #formatDateRange(Context, Formatter, long, long, int, String) formatDateRange}. 683 * 684 * @param context the context is required only if the time is shown 685 * @param formatter the Formatter used for formatting the date range. 686 * Note: be sure to call setLength(0) on StringBuilder passed to 687 * the Formatter constructor unless you want the results to accumulate. 688 * @param startMillis the start time in UTC milliseconds 689 * @param endMillis the end time in UTC milliseconds 690 * @param flags a bit mask of options See 691 * {@link #formatDateRange(Context, Formatter, long, long, int, String) formatDateRange} 692 * @return a string containing the formatted date/time range. 693 */ 694 public static Formatter formatDateRange(Context context, Formatter formatter, long startMillis, 695 long endMillis, int flags) { 696 return formatDateRange(context, formatter, startMillis, endMillis, flags, null); 697 } 698 699 /** 700 * Formats a date or a time range according to the local conventions. 701 * 702 * <p> 703 * Example output strings (date formats in these examples are shown using 704 * the US date format convention but that may change depending on the 705 * local settings): 706 * <ul> 707 * <li>10:15am</li> 708 * <li>3:00pm - 4:00pm</li> 709 * <li>3pm - 4pm</li> 710 * <li>3PM - 4PM</li> 711 * <li>08:00 - 17:00</li> 712 * <li>Oct 9</li> 713 * <li>Tue, Oct 9</li> 714 * <li>October 9, 2007</li> 715 * <li>Oct 9 - 10</li> 716 * <li>Oct 9 - 10, 2007</li> 717 * <li>Oct 28 - Nov 3, 2007</li> 718 * <li>Dec 31, 2007 - Jan 1, 2008</li> 719 * <li>Oct 9, 8:00am - Oct 10, 5:00pm</li> 720 * <li>12/31/2007 - 01/01/2008</li> 721 * </ul> 722 * 723 * <p> 724 * The flags argument is a bitmask of options from the following list: 725 * 726 * <ul> 727 * <li>FORMAT_SHOW_TIME</li> 728 * <li>FORMAT_SHOW_WEEKDAY</li> 729 * <li>FORMAT_SHOW_YEAR</li> 730 * <li>FORMAT_NO_YEAR</li> 731 * <li>FORMAT_SHOW_DATE</li> 732 * <li>FORMAT_NO_MONTH_DAY</li> 733 * <li>FORMAT_12HOUR</li> 734 * <li>FORMAT_24HOUR</li> 735 * <li>FORMAT_CAP_AMPM</li> 736 * <li>FORMAT_NO_NOON</li> 737 * <li>FORMAT_CAP_NOON</li> 738 * <li>FORMAT_NO_MIDNIGHT</li> 739 * <li>FORMAT_CAP_MIDNIGHT</li> 740 * <li>FORMAT_UTC</li> 741 * <li>FORMAT_ABBREV_TIME</li> 742 * <li>FORMAT_ABBREV_WEEKDAY</li> 743 * <li>FORMAT_ABBREV_MONTH</li> 744 * <li>FORMAT_ABBREV_ALL</li> 745 * <li>FORMAT_NUMERIC_DATE</li> 746 * </ul> 747 * 748 * <p> 749 * If FORMAT_SHOW_TIME is set, the time is shown as part of the date range. 750 * If the start and end time are the same, then just the start time is 751 * shown. 752 * 753 * <p> 754 * If FORMAT_SHOW_WEEKDAY is set, then the weekday is shown. 755 * 756 * <p> 757 * If FORMAT_SHOW_YEAR is set, then the year is always shown. 758 * If FORMAT_NO_YEAR is set, then the year is not shown. 759 * If neither FORMAT_SHOW_YEAR nor FORMAT_NO_YEAR are set, then the year 760 * is shown only if it is different from the current year, or if the start 761 * and end dates fall on different years. If both are set, 762 * FORMAT_SHOW_YEAR takes precedence. 763 * 764 * <p> 765 * Normally the date is shown unless the start and end day are the same. 766 * If FORMAT_SHOW_DATE is set, then the date is always shown, even for 767 * same day ranges. 768 * 769 * <p> 770 * If FORMAT_NO_MONTH_DAY is set, then if the date is shown, just the 771 * month name will be shown, not the day of the month. For example, 772 * "January, 2008" instead of "January 6 - 12, 2008". 773 * 774 * <p> 775 * If FORMAT_CAP_AMPM is set and 12-hour time is used, then the "AM" 776 * and "PM" are capitalized. You should not use this flag 777 * because in some locales these terms cannot be capitalized, and in 778 * many others it doesn't make sense to do so even though it is possible. 779 * 780 * <p> 781 * If FORMAT_NO_NOON is set and 12-hour time is used, then "12pm" is 782 * shown instead of "noon". 783 * 784 * <p> 785 * If FORMAT_CAP_NOON is set and 12-hour time is used, then "Noon" is 786 * shown instead of "noon". You should probably not use this flag 787 * because in many locales it will not make sense to capitalize 788 * the term. 789 * 790 * <p> 791 * If FORMAT_NO_MIDNIGHT is set and 12-hour time is used, then "12am" is 792 * shown instead of "midnight". 793 * 794 * <p> 795 * If FORMAT_CAP_MIDNIGHT is set and 12-hour time is used, then "Midnight" 796 * is shown instead of "midnight". You should probably not use this 797 * flag because in many locales it will not make sense to capitalize 798 * the term. 799 * 800 * <p> 801 * If FORMAT_12HOUR is set and the time is shown, then the time is 802 * shown in the 12-hour time format. You should not normally set this. 803 * Instead, let the time format be chosen automatically according to the 804 * system settings. If both FORMAT_12HOUR and FORMAT_24HOUR are set, then 805 * FORMAT_24HOUR takes precedence. 806 * 807 * <p> 808 * If FORMAT_24HOUR is set and the time is shown, then the time is 809 * shown in the 24-hour time format. You should not normally set this. 810 * Instead, let the time format be chosen automatically according to the 811 * system settings. If both FORMAT_12HOUR and FORMAT_24HOUR are set, then 812 * FORMAT_24HOUR takes precedence. 813 * 814 * <p> 815 * If FORMAT_UTC is set, then the UTC time zone is used for the start 816 * and end milliseconds unless a time zone is specified. If a time zone 817 * is specified it will be used regardless of the FORMAT_UTC flag. 818 * 819 * <p> 820 * If FORMAT_ABBREV_TIME is set and 12-hour time format is used, then the 821 * start and end times (if shown) are abbreviated by not showing the minutes 822 * if they are zero. For example, instead of "3:00pm" the time would be 823 * abbreviated to "3pm". 824 * 825 * <p> 826 * If FORMAT_ABBREV_WEEKDAY is set, then the weekday (if shown) is 827 * abbreviated to a 3-letter string. 828 * 829 * <p> 830 * If FORMAT_ABBREV_MONTH is set, then the month (if shown) is abbreviated 831 * to a 3-letter string. 832 * 833 * <p> 834 * If FORMAT_ABBREV_ALL is set, then the weekday and the month (if shown) 835 * are abbreviated to 3-letter strings. 836 * 837 * <p> 838 * If FORMAT_NUMERIC_DATE is set, then the date is shown in numeric format 839 * instead of using the name of the month. For example, "12/31/2008" 840 * instead of "December 31, 2008". 841 * 842 * <p> 843 * If the end date ends at 12:00am at the beginning of a day, it is 844 * formatted as the end of the previous day in two scenarios: 845 * <ul> 846 * <li>For single day events. This results in "8pm - midnight" instead of 847 * "Nov 10, 8pm - Nov 11, 12am".</li> 848 * <li>When the time is not displayed. This results in "Nov 10 - 11" for 849 * an event with a start date of Nov 10 and an end date of Nov 12 at 850 * 00:00.</li> 851 * </ul> 852 * 853 * @param context the context is required only if the time is shown 854 * @param formatter the Formatter used for formatting the date range. 855 * Note: be sure to call setLength(0) on StringBuilder passed to 856 * the Formatter constructor unless you want the results to accumulate. 857 * @param startMillis the start time in UTC milliseconds 858 * @param endMillis the end time in UTC milliseconds 859 * @param flags a bit mask of options 860 * @param timeZone the time zone to compute the string in. Use null for local 861 * or if the FORMAT_UTC flag is being used. 862 * 863 * @return the formatter with the formatted date/time range appended to the string buffer. 864 */ 865 public static Formatter formatDateRange(Context context, Formatter formatter, long startMillis, 866 long endMillis, int flags, String timeZone) { 867 Resources res = Resources.getSystem(); 868 boolean showTime = (flags & FORMAT_SHOW_TIME) != 0; 869 boolean showWeekDay = (flags & FORMAT_SHOW_WEEKDAY) != 0; 870 boolean showYear = (flags & FORMAT_SHOW_YEAR) != 0; 871 boolean noYear = (flags & FORMAT_NO_YEAR) != 0; 872 boolean useUTC = (flags & FORMAT_UTC) != 0; 873 boolean abbrevWeekDay = (flags & (FORMAT_ABBREV_WEEKDAY | FORMAT_ABBREV_ALL)) != 0; 874 boolean abbrevMonth = (flags & (FORMAT_ABBREV_MONTH | FORMAT_ABBREV_ALL)) != 0; 875 boolean noMonthDay = (flags & FORMAT_NO_MONTH_DAY) != 0; 876 boolean numericDate = (flags & FORMAT_NUMERIC_DATE) != 0; 877 878 // If we're getting called with a single instant in time (from 879 // e.g. formatDateTime(), below), then we can skip a lot of 880 // computation below that'd otherwise be thrown out. 881 boolean isInstant = (startMillis == endMillis); 882 883 Calendar startCalendar, endCalendar; 884 Time startDate = new Time(); 885 if (timeZone != null) { 886 startCalendar = Calendar.getInstance(TimeZone.getTimeZone(timeZone)); 887 } else if (useUTC) { 888 startCalendar = Calendar.getInstance(TimeZone.getTimeZone("UTC")); 889 } else { 890 startCalendar = Calendar.getInstance(); 891 } 892 startCalendar.setTimeInMillis(startMillis); 893 setTimeFromCalendar(startDate, startCalendar); 894 895 Time endDate = new Time(); 896 int dayDistance; 897 if (isInstant) { 898 endDate = startDate; 899 dayDistance = 0; 900 } else { 901 if (timeZone != null) { 902 endCalendar = Calendar.getInstance(TimeZone.getTimeZone(timeZone)); 903 } else if (useUTC) { 904 endCalendar = Calendar.getInstance(TimeZone.getTimeZone("UTC")); 905 } else { 906 endCalendar = Calendar.getInstance(); 907 } 908 endCalendar.setTimeInMillis(endMillis); 909 setTimeFromCalendar(endDate, endCalendar); 910 911 int startJulianDay = Time.getJulianDay(startMillis, startDate.gmtoff); 912 int endJulianDay = Time.getJulianDay(endMillis, endDate.gmtoff); 913 dayDistance = endJulianDay - startJulianDay; 914 } 915 916 if (!isInstant 917 && (endDate.hour | endDate.minute | endDate.second) == 0 918 && (!showTime || dayDistance <= 1)) { 919 endDate.monthDay -= 1; 920 endDate.normalize(true /* ignore isDst */); 921 } 922 923 int startDay = startDate.monthDay; 924 int startMonthNum = startDate.month; 925 int startYear = startDate.year; 926 927 int endDay = endDate.monthDay; 928 int endMonthNum = endDate.month; 929 int endYear = endDate.year; 930 931 String startWeekDayString = ""; 932 String endWeekDayString = ""; 933 if (showWeekDay) { 934 String weekDayFormat = ""; 935 if (abbrevWeekDay) { 936 weekDayFormat = ABBREV_WEEKDAY_FORMAT; 937 } else { 938 weekDayFormat = WEEKDAY_FORMAT; 939 } 940 startWeekDayString = startDate.format(weekDayFormat); 941 endWeekDayString = isInstant ? startWeekDayString : endDate.format(weekDayFormat); 942 } 943 944 String startTimeString = ""; 945 String endTimeString = ""; 946 if (showTime) { 947 String startTimeFormat = ""; 948 String endTimeFormat = ""; 949 boolean force24Hour = (flags & FORMAT_24HOUR) != 0; 950 boolean force12Hour = (flags & FORMAT_12HOUR) != 0; 951 boolean use24Hour; 952 if (force24Hour) { 953 use24Hour = true; 954 } else if (force12Hour) { 955 use24Hour = false; 956 } else { 957 use24Hour = DateFormat.is24HourFormat(context); 958 } 959 if (use24Hour) { 960 startTimeFormat = endTimeFormat = 961 res.getString(com.android.internal.R.string.hour_minute_24); 962 } else { 963 boolean abbrevTime = (flags & (FORMAT_ABBREV_TIME | FORMAT_ABBREV_ALL)) != 0; 964 boolean capAMPM = (flags & FORMAT_CAP_AMPM) != 0; 965 boolean noNoon = (flags & FORMAT_NO_NOON) != 0; 966 boolean capNoon = (flags & FORMAT_CAP_NOON) != 0; 967 boolean noMidnight = (flags & FORMAT_NO_MIDNIGHT) != 0; 968 boolean capMidnight = (flags & FORMAT_CAP_MIDNIGHT) != 0; 969 970 boolean startOnTheHour = startDate.minute == 0 && startDate.second == 0; 971 boolean endOnTheHour = endDate.minute == 0 && endDate.second == 0; 972 if (abbrevTime && startOnTheHour) { 973 if (capAMPM) { 974 startTimeFormat = res.getString(com.android.internal.R.string.hour_cap_ampm); 975 } else { 976 startTimeFormat = res.getString(com.android.internal.R.string.hour_ampm); 977 } 978 } else { 979 if (capAMPM) { 980 startTimeFormat = res.getString(com.android.internal.R.string.hour_minute_cap_ampm); 981 } else { 982 startTimeFormat = res.getString(com.android.internal.R.string.hour_minute_ampm); 983 } 984 } 985 986 // Don't waste time on setting endTimeFormat when 987 // we're dealing with an instant, where we'll never 988 // need the end point. (It's the same as the start 989 // point) 990 if (!isInstant) { 991 if (abbrevTime && endOnTheHour) { 992 if (capAMPM) { 993 endTimeFormat = res.getString(com.android.internal.R.string.hour_cap_ampm); 994 } else { 995 endTimeFormat = res.getString(com.android.internal.R.string.hour_ampm); 996 } 997 } else { 998 if (capAMPM) { 999 endTimeFormat = res.getString(com.android.internal.R.string.hour_minute_cap_ampm); 1000 } else { 1001 endTimeFormat = res.getString(com.android.internal.R.string.hour_minute_ampm); 1002 } 1003 } 1004 1005 if (endDate.hour == 12 && endOnTheHour && !noNoon) { 1006 if (capNoon) { 1007 endTimeFormat = res.getString(com.android.internal.R.string.Noon); 1008 } else { 1009 endTimeFormat = res.getString(com.android.internal.R.string.noon); 1010 } 1011 } else if (endDate.hour == 0 && endOnTheHour && !noMidnight) { 1012 if (capMidnight) { 1013 endTimeFormat = res.getString(com.android.internal.R.string.Midnight); 1014 } else { 1015 endTimeFormat = res.getString(com.android.internal.R.string.midnight); 1016 } 1017 } 1018 } 1019 1020 if (startDate.hour == 12 && startOnTheHour && !noNoon) { 1021 if (capNoon) { 1022 startTimeFormat = res.getString(com.android.internal.R.string.Noon); 1023 } else { 1024 startTimeFormat = res.getString(com.android.internal.R.string.noon); 1025 } 1026 // Don't show the start time starting at midnight. Show 1027 // 12am instead. 1028 } 1029 } 1030 1031 startTimeString = startDate.format(startTimeFormat); 1032 endTimeString = isInstant ? startTimeString : endDate.format(endTimeFormat); 1033 } 1034 1035 // Show the year if the user specified FORMAT_SHOW_YEAR or if 1036 // the starting and end years are different from each other 1037 // or from the current year. But don't show the year if the 1038 // user specified FORMAT_NO_YEAR. 1039 if (showYear) { 1040 // No code... just a comment for clarity. Keep showYear 1041 // on, as they enabled it with FORMAT_SHOW_YEAR. This 1042 // takes precedence over them setting FORMAT_NO_YEAR. 1043 } else if (noYear) { 1044 // They explicitly didn't want a year. 1045 showYear = false; 1046 } else if (startYear != endYear) { 1047 showYear = true; 1048 } else { 1049 // Show the year if it's not equal to the current year. 1050 Time currentTime = new Time(); 1051 currentTime.setToNow(); 1052 showYear = startYear != currentTime.year; 1053 } 1054 1055 String defaultDateFormat, fullFormat, dateRange; 1056 if (numericDate) { 1057 defaultDateFormat = res.getString(com.android.internal.R.string.numeric_date); 1058 } else if (showYear) { 1059 if (abbrevMonth) { 1060 if (noMonthDay) { 1061 defaultDateFormat = res.getString(com.android.internal.R.string.abbrev_month_year); 1062 } else { 1063 defaultDateFormat = res.getString(com.android.internal.R.string.abbrev_month_day_year); 1064 } 1065 } else { 1066 if (noMonthDay) { 1067 defaultDateFormat = res.getString(com.android.internal.R.string.month_year); 1068 } else { 1069 defaultDateFormat = res.getString(com.android.internal.R.string.month_day_year); 1070 } 1071 } 1072 } else { 1073 if (abbrevMonth) { 1074 if (noMonthDay) { 1075 defaultDateFormat = res.getString(com.android.internal.R.string.abbrev_month); 1076 } else { 1077 defaultDateFormat = res.getString(com.android.internal.R.string.abbrev_month_day); 1078 } 1079 } else { 1080 if (noMonthDay) { 1081 defaultDateFormat = res.getString(com.android.internal.R.string.month); 1082 } else { 1083 defaultDateFormat = res.getString(com.android.internal.R.string.month_day); 1084 } 1085 } 1086 } 1087 1088 if (showWeekDay) { 1089 if (showTime) { 1090 fullFormat = res.getString(com.android.internal.R.string.wday1_date1_time1_wday2_date2_time2); 1091 } else { 1092 fullFormat = res.getString(com.android.internal.R.string.wday1_date1_wday2_date2); 1093 } 1094 } else { 1095 if (showTime) { 1096 fullFormat = res.getString(com.android.internal.R.string.date1_time1_date2_time2); 1097 } else { 1098 fullFormat = res.getString(com.android.internal.R.string.date1_date2); 1099 } 1100 } 1101 1102 if (noMonthDay && startMonthNum == endMonthNum && startYear == endYear) { 1103 // Example: "January, 2008" 1104 return formatter.format("%s", startDate.format(defaultDateFormat)); 1105 } 1106 1107 if (startYear != endYear || noMonthDay) { 1108 // Different year or we are not showing the month day number. 1109 // Example: "December 31, 2007 - January 1, 2008" 1110 // Or: "January - February, 2008" 1111 String startDateString = startDate.format(defaultDateFormat); 1112 String endDateString = endDate.format(defaultDateFormat); 1113 1114 // The values that are used in a fullFormat string are specified 1115 // by position. 1116 return formatter.format(fullFormat, 1117 startWeekDayString, startDateString, startTimeString, 1118 endWeekDayString, endDateString, endTimeString); 1119 } 1120 1121 // Get the month, day, and year strings for the start and end dates 1122 String monthFormat; 1123 if (numericDate) { 1124 monthFormat = NUMERIC_MONTH_FORMAT; 1125 } else if (abbrevMonth) { 1126 monthFormat = 1127 res.getString(com.android.internal.R.string.short_format_month); 1128 } else { 1129 monthFormat = MONTH_FORMAT; 1130 } 1131 String startMonthString = startDate.format(monthFormat); 1132 String startMonthDayString = startDate.format(MONTH_DAY_FORMAT); 1133 String startYearString = startDate.format(YEAR_FORMAT); 1134 1135 String endMonthString = isInstant ? null : endDate.format(monthFormat); 1136 String endMonthDayString = isInstant ? null : endDate.format(MONTH_DAY_FORMAT); 1137 String endYearString = isInstant ? null : endDate.format(YEAR_FORMAT); 1138 1139 String startStandaloneMonthString = startMonthString; 1140 String endStandaloneMonthString = endMonthString; 1141 // We need standalone months for these strings in Persian (fa): http://b/6811327 1142 if (!numericDate && !abbrevMonth && Locale.getDefault().getLanguage().equals("fa")) { 1143 startStandaloneMonthString = startDate.format("%-B"); 1144 endStandaloneMonthString = endDate.format("%-B"); 1145 } 1146 1147 if (startMonthNum != endMonthNum) { 1148 // Same year, different month. 1149 // Example: "October 28 - November 3" 1150 // or: "Wed, Oct 31 - Sat, Nov 3, 2007" 1151 // or: "Oct 31, 8am - Sat, Nov 3, 2007, 5pm" 1152 1153 int index = 0; 1154 if (showWeekDay) index = 1; 1155 if (showYear) index += 2; 1156 if (showTime) index += 4; 1157 if (numericDate) index += 8; 1158 int resId = sameYearTable[index]; 1159 fullFormat = res.getString(resId); 1160 1161 // The values that are used in a fullFormat string are specified 1162 // by position. 1163 return formatter.format(fullFormat, 1164 startWeekDayString, startMonthString, startMonthDayString, 1165 startYearString, startTimeString, 1166 endWeekDayString, endMonthString, endMonthDayString, 1167 endYearString, endTimeString, 1168 startStandaloneMonthString, endStandaloneMonthString); 1169 } 1170 1171 if (startDay != endDay) { 1172 // Same month, different day. 1173 int index = 0; 1174 if (showWeekDay) index = 1; 1175 if (showYear) index += 2; 1176 if (showTime) index += 4; 1177 if (numericDate) index += 8; 1178 int resId = sameMonthTable[index]; 1179 fullFormat = res.getString(resId); 1180 1181 // The values that are used in a fullFormat string are specified 1182 // by position. 1183 return formatter.format(fullFormat, 1184 startWeekDayString, startMonthString, startMonthDayString, 1185 startYearString, startTimeString, 1186 endWeekDayString, endMonthString, endMonthDayString, 1187 endYearString, endTimeString, 1188 startStandaloneMonthString, endStandaloneMonthString); 1189 } 1190 1191 // Same start and end day 1192 boolean showDate = (flags & FORMAT_SHOW_DATE) != 0; 1193 1194 // If nothing was specified, then show the date. 1195 if (!showTime && !showDate && !showWeekDay) showDate = true; 1196 1197 // Compute the time string (example: "10:00 - 11:00 am") 1198 String timeString = ""; 1199 if (showTime) { 1200 // If the start and end time are the same, then just show the 1201 // start time. 1202 if (isInstant) { 1203 // Same start and end time. 1204 // Example: "10:15 AM" 1205 timeString = startTimeString; 1206 } else { 1207 // Example: "10:00 - 11:00 am" 1208 String timeFormat = res.getString(com.android.internal.R.string.time1_time2); 1209 // Don't use the user supplied Formatter because the result will pollute the buffer. 1210 timeString = String.format(timeFormat, startTimeString, endTimeString); 1211 } 1212 } 1213 1214 // Figure out which full format to use. 1215 fullFormat = ""; 1216 String dateString = ""; 1217 if (showDate) { 1218 dateString = startDate.format(defaultDateFormat); 1219 if (showWeekDay) { 1220 if (showTime) { 1221 // Example: "10:00 - 11:00 am, Tue, Oct 9" 1222 fullFormat = res.getString(com.android.internal.R.string.time_wday_date); 1223 } else { 1224 // Example: "Tue, Oct 9" 1225 fullFormat = res.getString(com.android.internal.R.string.wday_date); 1226 } 1227 } else { 1228 if (showTime) { 1229 // Example: "10:00 - 11:00 am, Oct 9" 1230 fullFormat = res.getString(com.android.internal.R.string.time_date); 1231 } else { 1232 // Example: "Oct 9" 1233 return formatter.format("%s", dateString); 1234 } 1235 } 1236 } else if (showWeekDay) { 1237 if (showTime) { 1238 // Example: "10:00 - 11:00 am, Tue" 1239 fullFormat = res.getString(com.android.internal.R.string.time_wday); 1240 } else { 1241 // Example: "Tue" 1242 return formatter.format("%s", startWeekDayString); 1243 } 1244 } else if (showTime) { 1245 return formatter.format("%s", timeString); 1246 } 1247 1248 // The values that are used in a fullFormat string are specified 1249 // by position. 1250 return formatter.format(fullFormat, timeString, startWeekDayString, dateString); 1251 } 1252 1253 private static void setTimeFromCalendar(Time t, Calendar c) { 1254 t.hour = c.get(Calendar.HOUR_OF_DAY); 1255 t.minute = c.get(Calendar.MINUTE); 1256 t.month = c.get(Calendar.MONTH); 1257 t.monthDay = c.get(Calendar.DAY_OF_MONTH); 1258 t.second = c.get(Calendar.SECOND); 1259 t.weekDay = c.get(Calendar.DAY_OF_WEEK) - 1; 1260 t.year = c.get(Calendar.YEAR); 1261 t.yearDay = c.get(Calendar.DAY_OF_YEAR); 1262 t.isDst = (c.get(Calendar.DST_OFFSET) != 0) ? 1 : 0; 1263 t.gmtoff = c.get(Calendar.ZONE_OFFSET) + c.get(Calendar.DST_OFFSET); 1264 t.timezone = c.getTimeZone().getID(); 1265 } 1266 1267 /** 1268 * Formats a date or a time according to the local conventions. There are 1269 * lots of options that allow the caller to control, for example, if the 1270 * time is shown, if the day of the week is shown, if the month name is 1271 * abbreviated, if noon is shown instead of 12pm, and so on. For the 1272 * complete list of options, see the documentation for 1273 * {@link #formatDateRange}. 1274 * <p> 1275 * Example output strings (date formats in these examples are shown using 1276 * the US date format convention but that may change depending on the 1277 * local settings): 1278 * <ul> 1279 * <li>10:15am</li> 1280 * <li>3:00pm</li> 1281 * <li>3pm</li> 1282 * <li>3PM</li> 1283 * <li>08:00</li> 1284 * <li>17:00</li> 1285 * <li>noon</li> 1286 * <li>Noon</li> 1287 * <li>midnight</li> 1288 * <li>Midnight</li> 1289 * <li>Oct 31</li> 1290 * <li>Oct 31, 2007</li> 1291 * <li>October 31, 2007</li> 1292 * <li>10am, Oct 31</li> 1293 * <li>17:00, Oct 31</li> 1294 * <li>Wed</li> 1295 * <li>Wednesday</li> 1296 * <li>10am, Wed, Oct 31</li> 1297 * <li>Wed, Oct 31</li> 1298 * <li>Wednesday, Oct 31</li> 1299 * <li>Wed, Oct 31, 2007</li> 1300 * <li>Wed, October 31</li> 1301 * <li>10/31/2007</li> 1302 * </ul> 1303 * 1304 * @param context the context is required only if the time is shown 1305 * @param millis a point in time in UTC milliseconds 1306 * @param flags a bit mask of formatting options 1307 * @return a string containing the formatted date/time. 1308 */ 1309 public static String formatDateTime(Context context, long millis, int flags) { 1310 return formatDateRange(context, millis, millis, flags); 1311 } 1312 1313 /** 1314 * @return a relative time string to display the time expressed by millis. Times 1315 * are counted starting at midnight, which means that assuming that the current 1316 * time is March 31st, 0:30: 1317 * <ul> 1318 * <li>"millis=0:10 today" will be displayed as "0:10"</li> 1319 * <li>"millis=11:30pm the day before" will be displayed as "Mar 30"</li> 1320 * </ul> 1321 * If the given millis is in a different year, then the full date is 1322 * returned in numeric format (e.g., "10/12/2008"). 1323 * 1324 * @param withPreposition If true, the string returned will include the correct 1325 * preposition ("at 9:20am", "on 10/12/2008" or "on May 29"). 1326 */ 1327 public static CharSequence getRelativeTimeSpanString(Context c, long millis, 1328 boolean withPreposition) { 1329 1330 String result; 1331 long now = System.currentTimeMillis(); 1332 long span = Math.abs(now - millis); 1333 1334 synchronized (DateUtils.class) { 1335 if (sNowTime == null) { 1336 sNowTime = new Time(); 1337 } 1338 1339 if (sThenTime == null) { 1340 sThenTime = new Time(); 1341 } 1342 1343 sNowTime.set(now); 1344 sThenTime.set(millis); 1345 1346 int prepositionId; 1347 if (span < DAY_IN_MILLIS && sNowTime.weekDay == sThenTime.weekDay) { 1348 // Same day 1349 int flags = FORMAT_SHOW_TIME; 1350 result = formatDateRange(c, millis, millis, flags); 1351 prepositionId = R.string.preposition_for_time; 1352 } else if (sNowTime.year != sThenTime.year) { 1353 // Different years 1354 int flags = FORMAT_SHOW_DATE | FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE; 1355 result = formatDateRange(c, millis, millis, flags); 1356 1357 // This is a date (like "10/31/2008" so use the date preposition) 1358 prepositionId = R.string.preposition_for_date; 1359 } else { 1360 // Default 1361 int flags = FORMAT_SHOW_DATE | FORMAT_ABBREV_MONTH; 1362 result = formatDateRange(c, millis, millis, flags); 1363 prepositionId = R.string.preposition_for_date; 1364 } 1365 if (withPreposition) { 1366 Resources res = c.getResources(); 1367 result = res.getString(prepositionId, result); 1368 } 1369 } 1370 return result; 1371 } 1372 1373 /** 1374 * Convenience function to return relative time string without preposition. 1375 * @param c context for resources 1376 * @param millis time in milliseconds 1377 * @return {@link CharSequence} containing relative time. 1378 * @see #getRelativeTimeSpanString(Context, long, boolean) 1379 */ 1380 public static CharSequence getRelativeTimeSpanString(Context c, long millis) { 1381 return getRelativeTimeSpanString(c, millis, false /* no preposition */); 1382 } 1383 1384 private static Time sNowTime; 1385 private static Time sThenTime; 1386 } 1387