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.io.IOException; 26 import java.util.Calendar; 27 import java.util.Date; 28 import java.util.Formatter; 29 import java.util.GregorianCalendar; 30 import java.util.Locale; 31 import java.util.TimeZone; 32 33 import libcore.icu.DateIntervalFormat; 34 import libcore.icu.LocaleData; 35 36 /** 37 * This class contains various date-related utilities for creating text for things like 38 * elapsed time and date ranges, strings for days of the week and months, and AM/PM text etc. 39 */ 40 public class DateUtils 41 { 42 private static final Object sLock = new Object(); 43 private static Configuration sLastConfig; 44 private static String sElapsedFormatMMSS; 45 private static String sElapsedFormatHMMSS; 46 47 public static final long SECOND_IN_MILLIS = 1000; 48 public static final long MINUTE_IN_MILLIS = SECOND_IN_MILLIS * 60; 49 public static final long HOUR_IN_MILLIS = MINUTE_IN_MILLIS * 60; 50 public static final long DAY_IN_MILLIS = HOUR_IN_MILLIS * 24; 51 public static final long WEEK_IN_MILLIS = DAY_IN_MILLIS * 7; 52 /** 53 * This constant is actually the length of 364 days, not of a year! 54 */ 55 public static final long YEAR_IN_MILLIS = WEEK_IN_MILLIS * 52; 56 57 // The following FORMAT_* symbols are used for specifying the format of 58 // dates and times in the formatDateRange method. 59 public static final int FORMAT_SHOW_TIME = 0x00001; 60 public static final int FORMAT_SHOW_WEEKDAY = 0x00002; 61 public static final int FORMAT_SHOW_YEAR = 0x00004; 62 public static final int FORMAT_NO_YEAR = 0x00008; 63 public static final int FORMAT_SHOW_DATE = 0x00010; 64 public static final int FORMAT_NO_MONTH_DAY = 0x00020; 65 @Deprecated 66 public static final int FORMAT_12HOUR = 0x00040; 67 @Deprecated 68 public static final int FORMAT_24HOUR = 0x00080; 69 @Deprecated 70 public static final int FORMAT_CAP_AMPM = 0x00100; 71 public static final int FORMAT_NO_NOON = 0x00200; 72 @Deprecated 73 public static final int FORMAT_CAP_NOON = 0x00400; 74 public static final int FORMAT_NO_MIDNIGHT = 0x00800; 75 @Deprecated 76 public static final int FORMAT_CAP_MIDNIGHT = 0x01000; 77 /** 78 * @deprecated Use 79 * {@link #formatDateRange(Context, Formatter, long, long, int, String) formatDateRange} 80 * and pass in {@link Time#TIMEZONE_UTC Time.TIMEZONE_UTC} for the timeZone instead. 81 */ 82 @Deprecated 83 public static final int FORMAT_UTC = 0x02000; 84 public static final int FORMAT_ABBREV_TIME = 0x04000; 85 public static final int FORMAT_ABBREV_WEEKDAY = 0x08000; 86 public static final int FORMAT_ABBREV_MONTH = 0x10000; 87 public static final int FORMAT_NUMERIC_DATE = 0x20000; 88 public static final int FORMAT_ABBREV_RELATIVE = 0x40000; 89 public static final int FORMAT_ABBREV_ALL = 0x80000; 90 @Deprecated 91 public static final int FORMAT_CAP_NOON_MIDNIGHT = (FORMAT_CAP_NOON | FORMAT_CAP_MIDNIGHT); 92 @Deprecated 93 public static final int FORMAT_NO_NOON_MIDNIGHT = (FORMAT_NO_NOON | FORMAT_NO_MIDNIGHT); 94 95 // Date and time format strings that are constant and don't need to be 96 // translated. 97 /** 98 * This is not actually the preferred 24-hour date format in all locales. 99 * @deprecated Use {@link java.text.SimpleDateFormat} instead. 100 */ 101 @Deprecated 102 public static final String HOUR_MINUTE_24 = "%H:%M"; 103 public static final String MONTH_FORMAT = "%B"; 104 /** 105 * This is not actually a useful month name in all locales. 106 * @deprecated Use {@link java.text.SimpleDateFormat} instead. 107 */ 108 @Deprecated 109 public static final String ABBREV_MONTH_FORMAT = "%b"; 110 public static final String NUMERIC_MONTH_FORMAT = "%m"; 111 public static final String MONTH_DAY_FORMAT = "%-d"; 112 public static final String YEAR_FORMAT = "%Y"; 113 public static final String YEAR_FORMAT_TWO_DIGITS = "%g"; 114 public static final String WEEKDAY_FORMAT = "%A"; 115 public static final String ABBREV_WEEKDAY_FORMAT = "%a"; 116 117 /** @deprecated Do not use. */ 118 public static final int[] sameYearTable = null; 119 120 /** @deprecated Do not use. */ 121 public static final int[] sameMonthTable = null; 122 123 /** 124 * Request the full spelled-out name. For use with the 'abbrev' parameter of 125 * {@link #getDayOfWeekString} and {@link #getMonthString}. 126 * 127 * @more <p> 128 * e.g. "Sunday" or "January" 129 * @deprecated Use {@link java.text.SimpleDateFormat} instead. 130 */ 131 @Deprecated 132 public static final int LENGTH_LONG = 10; 133 134 /** 135 * Request an abbreviated version of the name. For use with the 'abbrev' 136 * parameter of {@link #getDayOfWeekString} and {@link #getMonthString}. 137 * 138 * @more <p> 139 * e.g. "Sun" or "Jan" 140 * @deprecated Use {@link java.text.SimpleDateFormat} instead. 141 */ 142 @Deprecated 143 public static final int LENGTH_MEDIUM = 20; 144 145 /** 146 * Request a shorter abbreviated version of the name. 147 * For use with the 'abbrev' parameter of {@link #getDayOfWeekString} and {@link #getMonthString}. 148 * @more 149 * <p>e.g. "Su" or "Jan" 150 * <p>In most languages, the results returned for LENGTH_SHORT will be the same as 151 * the results returned for {@link #LENGTH_MEDIUM}. 152 * @deprecated Use {@link java.text.SimpleDateFormat} instead. 153 */ 154 @Deprecated 155 public static final int LENGTH_SHORT = 30; 156 157 /** 158 * Request an even shorter abbreviated version of the name. 159 * Do not use this. Currently this will always return the same result 160 * as {@link #LENGTH_SHORT}. 161 * @deprecated Use {@link java.text.SimpleDateFormat} instead. 162 */ 163 @Deprecated 164 public static final int LENGTH_SHORTER = 40; 165 166 /** 167 * Request an even shorter abbreviated version of the name. 168 * For use with the 'abbrev' parameter of {@link #getDayOfWeekString} and {@link #getMonthString}. 169 * @more 170 * <p>e.g. "S", "T", "T" or "J" 171 * <p>In some languages, the results returned for LENGTH_SHORTEST will be the same as 172 * the results returned for {@link #LENGTH_SHORT}. 173 * @deprecated Use {@link java.text.SimpleDateFormat} instead. 174 */ 175 @Deprecated 176 public static final int LENGTH_SHORTEST = 50; 177 178 /** 179 * Return a string for the day of the week. 180 * @param dayOfWeek One of {@link Calendar#SUNDAY Calendar.SUNDAY}, 181 * {@link Calendar#MONDAY Calendar.MONDAY}, etc. 182 * @param abbrev One of {@link #LENGTH_LONG}, {@link #LENGTH_SHORT}, 183 * {@link #LENGTH_MEDIUM}, or {@link #LENGTH_SHORTEST}. 184 * Note that in most languages, {@link #LENGTH_SHORT} 185 * will return the same as {@link #LENGTH_MEDIUM}. 186 * Undefined lengths will return {@link #LENGTH_MEDIUM} 187 * but may return something different in the future. 188 * @throws IndexOutOfBoundsException if the dayOfWeek is out of bounds. 189 * @deprecated Use {@link java.text.SimpleDateFormat} instead. 190 */ 191 @Deprecated getDayOfWeekString(int dayOfWeek, int abbrev)192 public static String getDayOfWeekString(int dayOfWeek, int abbrev) { 193 LocaleData d = LocaleData.get(Locale.getDefault()); 194 String[] names; 195 switch (abbrev) { 196 case LENGTH_LONG: names = d.longWeekdayNames; break; 197 case LENGTH_MEDIUM: names = d.shortWeekdayNames; break; 198 case LENGTH_SHORT: names = d.shortWeekdayNames; break; // TODO 199 case LENGTH_SHORTER: names = d.shortWeekdayNames; break; // TODO 200 case LENGTH_SHORTEST: names = d.tinyWeekdayNames; break; 201 default: names = d.shortWeekdayNames; break; 202 } 203 return names[dayOfWeek]; 204 } 205 206 /** 207 * Return a localized string for AM or PM. 208 * @param ampm Either {@link Calendar#AM Calendar.AM} or {@link Calendar#PM Calendar.PM}. 209 * @throws IndexOutOfBoundsException if the ampm is out of bounds. 210 * @return Localized version of "AM" or "PM". 211 * @deprecated Use {@link java.text.SimpleDateFormat} instead. 212 */ 213 @Deprecated getAMPMString(int ampm)214 public static String getAMPMString(int ampm) { 215 return LocaleData.get(Locale.getDefault()).amPm[ampm - Calendar.AM]; 216 } 217 218 /** 219 * Return a localized string for the month of the year. 220 * @param month One of {@link Calendar#JANUARY Calendar.JANUARY}, 221 * {@link Calendar#FEBRUARY Calendar.FEBRUARY}, etc. 222 * @param abbrev One of {@link #LENGTH_LONG}, {@link #LENGTH_MEDIUM}, 223 * or {@link #LENGTH_SHORTEST}. 224 * Undefined lengths will return {@link #LENGTH_MEDIUM} 225 * but may return something different in the future. 226 * @return Localized month of the year. 227 * @deprecated Use {@link java.text.SimpleDateFormat} instead. 228 */ 229 @Deprecated getMonthString(int month, int abbrev)230 public static String getMonthString(int month, int abbrev) { 231 LocaleData d = LocaleData.get(Locale.getDefault()); 232 String[] names; 233 switch (abbrev) { 234 case LENGTH_LONG: names = d.longMonthNames; break; 235 case LENGTH_MEDIUM: names = d.shortMonthNames; break; 236 case LENGTH_SHORT: names = d.shortMonthNames; break; 237 case LENGTH_SHORTER: names = d.shortMonthNames; break; 238 case LENGTH_SHORTEST: names = d.tinyMonthNames; break; 239 default: names = d.shortMonthNames; break; 240 } 241 return names[month]; 242 } 243 244 /** 245 * Returns a string describing the elapsed time since startTime. 246 * @param startTime some time in the past. 247 * @return a String object containing the elapsed time. 248 * @see #getRelativeTimeSpanString(long, long, long) 249 */ getRelativeTimeSpanString(long startTime)250 public static CharSequence getRelativeTimeSpanString(long startTime) { 251 return getRelativeTimeSpanString(startTime, System.currentTimeMillis(), MINUTE_IN_MILLIS); 252 } 253 254 /** 255 * Returns a string describing 'time' as a time relative to 'now'. 256 * <p> 257 * Time spans in the past are formatted like "42 minutes ago". 258 * Time spans in the future are formatted like "in 42 minutes". 259 * 260 * @param time the time to describe, in milliseconds 261 * @param now the current time in milliseconds 262 * @param minResolution the minimum timespan to report. For example, a time 3 seconds in the 263 * past will be reported as "0 minutes ago" if this is set to MINUTE_IN_MILLIS. Pass one of 264 * 0, MINUTE_IN_MILLIS, HOUR_IN_MILLIS, DAY_IN_MILLIS, WEEK_IN_MILLIS 265 */ getRelativeTimeSpanString(long time, long now, long minResolution)266 public static CharSequence getRelativeTimeSpanString(long time, long now, long minResolution) { 267 int flags = FORMAT_SHOW_DATE | FORMAT_SHOW_YEAR | FORMAT_ABBREV_MONTH; 268 return getRelativeTimeSpanString(time, now, minResolution, flags); 269 } 270 271 /** 272 * Returns a string describing 'time' as a time relative to 'now'. 273 * <p> 274 * Time spans in the past are formatted like "42 minutes ago". Time spans in 275 * the future are formatted like "in 42 minutes". 276 * <p> 277 * Can use {@link #FORMAT_ABBREV_RELATIVE} flag to use abbreviated relative 278 * times, like "42 mins ago". 279 * 280 * @param time the time to describe, in milliseconds 281 * @param now the current time in milliseconds 282 * @param minResolution the minimum timespan to report. For example, a time 283 * 3 seconds in the past will be reported as "0 minutes ago" if 284 * this is set to MINUTE_IN_MILLIS. Pass one of 0, 285 * MINUTE_IN_MILLIS, HOUR_IN_MILLIS, DAY_IN_MILLIS, 286 * WEEK_IN_MILLIS 287 * @param flags a bit mask of formatting options, such as 288 * {@link #FORMAT_NUMERIC_DATE} or 289 * {@link #FORMAT_ABBREV_RELATIVE} 290 */ getRelativeTimeSpanString(long time, long now, long minResolution, int flags)291 public static CharSequence getRelativeTimeSpanString(long time, long now, long minResolution, 292 int flags) { 293 Resources r = Resources.getSystem(); 294 boolean abbrevRelative = (flags & (FORMAT_ABBREV_RELATIVE | FORMAT_ABBREV_ALL)) != 0; 295 296 boolean past = (now >= time); 297 long duration = Math.abs(now - time); 298 299 int resId; 300 long count; 301 if (duration < MINUTE_IN_MILLIS && minResolution < MINUTE_IN_MILLIS) { 302 count = duration / SECOND_IN_MILLIS; 303 if (past) { 304 if (abbrevRelative) { 305 resId = com.android.internal.R.plurals.abbrev_num_seconds_ago; 306 } else { 307 resId = com.android.internal.R.plurals.num_seconds_ago; 308 } 309 } else { 310 if (abbrevRelative) { 311 resId = com.android.internal.R.plurals.abbrev_in_num_seconds; 312 } else { 313 resId = com.android.internal.R.plurals.in_num_seconds; 314 } 315 } 316 } else if (duration < HOUR_IN_MILLIS && minResolution < HOUR_IN_MILLIS) { 317 count = duration / MINUTE_IN_MILLIS; 318 if (past) { 319 if (abbrevRelative) { 320 resId = com.android.internal.R.plurals.abbrev_num_minutes_ago; 321 } else { 322 resId = com.android.internal.R.plurals.num_minutes_ago; 323 } 324 } else { 325 if (abbrevRelative) { 326 resId = com.android.internal.R.plurals.abbrev_in_num_minutes; 327 } else { 328 resId = com.android.internal.R.plurals.in_num_minutes; 329 } 330 } 331 } else if (duration < DAY_IN_MILLIS && minResolution < DAY_IN_MILLIS) { 332 count = duration / HOUR_IN_MILLIS; 333 if (past) { 334 if (abbrevRelative) { 335 resId = com.android.internal.R.plurals.abbrev_num_hours_ago; 336 } else { 337 resId = com.android.internal.R.plurals.num_hours_ago; 338 } 339 } else { 340 if (abbrevRelative) { 341 resId = com.android.internal.R.plurals.abbrev_in_num_hours; 342 } else { 343 resId = com.android.internal.R.plurals.in_num_hours; 344 } 345 } 346 } else if (duration < WEEK_IN_MILLIS && minResolution < WEEK_IN_MILLIS) { 347 return getRelativeDayString(r, time, now); 348 } else { 349 // We know that we won't be showing the time, so it is safe to pass 350 // in a null context. 351 return formatDateRange(null, time, time, flags); 352 } 353 354 String format = r.getQuantityString(resId, (int) count); 355 return String.format(format, count); 356 } 357 358 /** 359 * Return string describing the elapsed time since startTime formatted like 360 * "[relative time/date], [time]". 361 * <p> 362 * Example output strings for the US date format. 363 * <ul> 364 * <li>3 mins ago, 10:15 AM</li> 365 * <li>yesterday, 12:20 PM</li> 366 * <li>Dec 12, 4:12 AM</li> 367 * <li>11/14/2007, 8:20 AM</li> 368 * </ul> 369 * 370 * @param time some time in the past. 371 * @param minResolution the minimum elapsed time (in milliseconds) to report 372 * when showing relative times. For example, a time 3 seconds in 373 * the past will be reported as "0 minutes ago" if this is set to 374 * {@link #MINUTE_IN_MILLIS}. 375 * @param transitionResolution the elapsed time (in milliseconds) at which 376 * to stop reporting relative measurements. Elapsed times greater 377 * than this resolution will default to normal date formatting. 378 * For example, will transition from "6 days ago" to "Dec 12" 379 * when using {@link #WEEK_IN_MILLIS}. 380 */ getRelativeDateTimeString(Context c, long time, long minResolution, long transitionResolution, int flags)381 public static CharSequence getRelativeDateTimeString(Context c, long time, long minResolution, 382 long transitionResolution, int flags) { 383 Resources r = Resources.getSystem(); 384 385 long now = System.currentTimeMillis(); 386 long duration = Math.abs(now - time); 387 388 // getRelativeTimeSpanString() doesn't correctly format relative dates 389 // above a week or exact dates below a day, so clamp 390 // transitionResolution as needed. 391 if (transitionResolution > WEEK_IN_MILLIS) { 392 transitionResolution = WEEK_IN_MILLIS; 393 } else if (transitionResolution < DAY_IN_MILLIS) { 394 transitionResolution = DAY_IN_MILLIS; 395 } 396 397 CharSequence timeClause = formatDateRange(c, time, time, FORMAT_SHOW_TIME); 398 399 String result; 400 if (duration < transitionResolution) { 401 CharSequence relativeClause = getRelativeTimeSpanString(time, now, minResolution, flags); 402 result = r.getString(com.android.internal.R.string.relative_time, relativeClause, timeClause); 403 } else { 404 CharSequence dateClause = getRelativeTimeSpanString(c, time, false); 405 result = r.getString(com.android.internal.R.string.date_time, dateClause, timeClause); 406 } 407 408 return result; 409 } 410 411 /** 412 * Returns a string describing a day relative to the current day. For example if the day is 413 * today this function returns "Today", if the day was a week ago it returns "7 days ago", and 414 * if the day is in 2 weeks it returns "in 14 days". 415 * 416 * @param r the resources 417 * @param day the relative day to describe in UTC milliseconds 418 * @param today the current time in UTC milliseconds 419 */ getRelativeDayString(Resources r, long day, long today)420 private static final String getRelativeDayString(Resources r, long day, long today) { 421 Locale locale = r.getConfiguration().locale; 422 if (locale == null) { 423 locale = Locale.getDefault(); 424 } 425 426 // TODO: use TimeZone.getOffset instead. 427 Time startTime = new Time(); 428 startTime.set(day); 429 int startDay = Time.getJulianDay(day, startTime.gmtoff); 430 431 Time currentTime = new Time(); 432 currentTime.set(today); 433 int currentDay = Time.getJulianDay(today, currentTime.gmtoff); 434 435 int days = Math.abs(currentDay - startDay); 436 boolean past = (today > day); 437 438 // TODO: some locales name other days too, such as de_DE's "Vorgestern" (today - 2). 439 if (days == 1) { 440 if (past) { 441 return LocaleData.get(locale).yesterday; 442 } else { 443 return LocaleData.get(locale).tomorrow; 444 } 445 } else if (days == 0) { 446 return LocaleData.get(locale).today; 447 } 448 449 int resId; 450 if (past) { 451 resId = com.android.internal.R.plurals.num_days_ago; 452 } else { 453 resId = com.android.internal.R.plurals.in_num_days; 454 } 455 456 String format = r.getQuantityString(resId, days); 457 return String.format(format, days); 458 } 459 initFormatStrings()460 private static void initFormatStrings() { 461 synchronized (sLock) { 462 initFormatStringsLocked(); 463 } 464 } 465 initFormatStringsLocked()466 private static void initFormatStringsLocked() { 467 Resources r = Resources.getSystem(); 468 Configuration cfg = r.getConfiguration(); 469 if (sLastConfig == null || !sLastConfig.equals(cfg)) { 470 sLastConfig = cfg; 471 sElapsedFormatMMSS = r.getString(com.android.internal.R.string.elapsed_time_short_format_mm_ss); 472 sElapsedFormatHMMSS = r.getString(com.android.internal.R.string.elapsed_time_short_format_h_mm_ss); 473 } 474 } 475 476 /** 477 * Return given duration in a human-friendly format. For example, "4 478 * minutes" or "1 second". Returns only largest meaningful unit of time, 479 * from seconds up to hours. 480 * 481 * @hide 482 */ formatDuration(long millis)483 public static CharSequence formatDuration(long millis) { 484 final Resources res = Resources.getSystem(); 485 if (millis >= HOUR_IN_MILLIS) { 486 final int hours = (int) ((millis + 1800000) / HOUR_IN_MILLIS); 487 return res.getQuantityString( 488 com.android.internal.R.plurals.duration_hours, hours, hours); 489 } else if (millis >= MINUTE_IN_MILLIS) { 490 final int minutes = (int) ((millis + 30000) / MINUTE_IN_MILLIS); 491 return res.getQuantityString( 492 com.android.internal.R.plurals.duration_minutes, minutes, minutes); 493 } else { 494 final int seconds = (int) ((millis + 500) / SECOND_IN_MILLIS); 495 return res.getQuantityString( 496 com.android.internal.R.plurals.duration_seconds, seconds, seconds); 497 } 498 } 499 500 /** 501 * Formats an elapsed time in the form "MM:SS" or "H:MM:SS" 502 * for display on the call-in-progress screen. 503 * @param elapsedSeconds the elapsed time in seconds. 504 */ formatElapsedTime(long elapsedSeconds)505 public static String formatElapsedTime(long elapsedSeconds) { 506 return formatElapsedTime(null, elapsedSeconds); 507 } 508 509 /** 510 * Formats an elapsed time in a format like "MM:SS" or "H:MM:SS" (using a form 511 * suited to the current locale), similar to that used on the call-in-progress 512 * screen. 513 * 514 * @param recycle {@link StringBuilder} to recycle, or null to use a temporary one. 515 * @param elapsedSeconds the elapsed time in seconds. 516 */ formatElapsedTime(StringBuilder recycle, long elapsedSeconds)517 public static String formatElapsedTime(StringBuilder recycle, long elapsedSeconds) { 518 // Break the elapsed seconds into hours, minutes, and seconds. 519 long hours = 0; 520 long minutes = 0; 521 long seconds = 0; 522 if (elapsedSeconds >= 3600) { 523 hours = elapsedSeconds / 3600; 524 elapsedSeconds -= hours * 3600; 525 } 526 if (elapsedSeconds >= 60) { 527 minutes = elapsedSeconds / 60; 528 elapsedSeconds -= minutes * 60; 529 } 530 seconds = elapsedSeconds; 531 532 // Create a StringBuilder if we weren't given one to recycle. 533 // TODO: if we cared, we could have a thread-local temporary StringBuilder. 534 StringBuilder sb = recycle; 535 if (sb == null) { 536 sb = new StringBuilder(8); 537 } else { 538 sb.setLength(0); 539 } 540 541 // Format the broken-down time in a locale-appropriate way. 542 // TODO: use icu4c when http://unicode.org/cldr/trac/ticket/3407 is fixed. 543 Formatter f = new Formatter(sb, Locale.getDefault()); 544 initFormatStrings(); 545 if (hours > 0) { 546 return f.format(sElapsedFormatHMMSS, hours, minutes, seconds).toString(); 547 } else { 548 return f.format(sElapsedFormatMMSS, minutes, seconds).toString(); 549 } 550 } 551 552 /** 553 * Format a date / time such that if the then is on the same day as now, it shows 554 * just the time and if it's a different day, it shows just the date. 555 * 556 * <p>The parameters dateFormat and timeFormat should each be one of 557 * {@link java.text.DateFormat#DEFAULT}, 558 * {@link java.text.DateFormat#FULL}, 559 * {@link java.text.DateFormat#LONG}, 560 * {@link java.text.DateFormat#MEDIUM} 561 * or 562 * {@link java.text.DateFormat#SHORT} 563 * 564 * @param then the date to format 565 * @param now the base time 566 * @param dateStyle how to format the date portion. 567 * @param timeStyle how to format the time portion. 568 */ formatSameDayTime(long then, long now, int dateStyle, int timeStyle)569 public static final CharSequence formatSameDayTime(long then, long now, 570 int dateStyle, int timeStyle) { 571 Calendar thenCal = new GregorianCalendar(); 572 thenCal.setTimeInMillis(then); 573 Date thenDate = thenCal.getTime(); 574 Calendar nowCal = new GregorianCalendar(); 575 nowCal.setTimeInMillis(now); 576 577 java.text.DateFormat f; 578 579 if (thenCal.get(Calendar.YEAR) == nowCal.get(Calendar.YEAR) 580 && thenCal.get(Calendar.MONTH) == nowCal.get(Calendar.MONTH) 581 && thenCal.get(Calendar.DAY_OF_MONTH) == nowCal.get(Calendar.DAY_OF_MONTH)) { 582 f = java.text.DateFormat.getTimeInstance(timeStyle); 583 } else { 584 f = java.text.DateFormat.getDateInstance(dateStyle); 585 } 586 return f.format(thenDate); 587 } 588 589 /** 590 * @return true if the supplied when is today else false 591 */ isToday(long when)592 public static boolean isToday(long when) { 593 Time time = new Time(); 594 time.set(when); 595 596 int thenYear = time.year; 597 int thenMonth = time.month; 598 int thenMonthDay = time.monthDay; 599 600 time.set(System.currentTimeMillis()); 601 return (thenYear == time.year) 602 && (thenMonth == time.month) 603 && (thenMonthDay == time.monthDay); 604 } 605 606 /** 607 * Formats a date or a time range according to the local conventions. 608 * <p> 609 * Note that this is a convenience method. Using it involves creating an 610 * internal {@link java.util.Formatter} instance on-the-fly, which is 611 * somewhat costly in terms of memory and time. This is probably acceptable 612 * if you use the method only rarely, but if you rely on it for formatting a 613 * large number of dates, consider creating and reusing your own 614 * {@link java.util.Formatter} instance and use the version of 615 * {@link #formatDateRange(Context, long, long, int) formatDateRange} 616 * that takes a {@link java.util.Formatter}. 617 * 618 * @param context the context is required only if the time is shown 619 * @param startMillis the start time in UTC milliseconds 620 * @param endMillis the end time in UTC milliseconds 621 * @param flags a bit mask of options See 622 * {@link #formatDateRange(Context, Formatter, long, long, int, String) formatDateRange} 623 * @return a string containing the formatted date/time range. 624 */ formatDateRange(Context context, long startMillis, long endMillis, int flags)625 public static String formatDateRange(Context context, long startMillis, 626 long endMillis, int flags) { 627 Formatter f = new Formatter(new StringBuilder(50), Locale.getDefault()); 628 return formatDateRange(context, f, startMillis, endMillis, flags).toString(); 629 } 630 631 /** 632 * Formats a date or a time range according to the local conventions. 633 * <p> 634 * Note that this is a convenience method for formatting the date or 635 * time range in the local time zone. If you want to specify the time 636 * zone please use 637 * {@link #formatDateRange(Context, Formatter, long, long, int, String) formatDateRange}. 638 * 639 * @param context the context is required only if the time is shown 640 * @param formatter the Formatter used for formatting the date range. 641 * Note: be sure to call setLength(0) on StringBuilder passed to 642 * the Formatter constructor unless you want the results to accumulate. 643 * @param startMillis the start time in UTC milliseconds 644 * @param endMillis the end time in UTC milliseconds 645 * @param flags a bit mask of options See 646 * {@link #formatDateRange(Context, Formatter, long, long, int, String) formatDateRange} 647 * @return a string containing the formatted date/time range. 648 */ formatDateRange(Context context, Formatter formatter, long startMillis, long endMillis, int flags)649 public static Formatter formatDateRange(Context context, Formatter formatter, long startMillis, 650 long endMillis, int flags) { 651 return formatDateRange(context, formatter, startMillis, endMillis, flags, null); 652 } 653 654 /** 655 * Formats a date or a time range according to the local conventions. 656 * 657 * <p> 658 * Example output strings (date formats in these examples are shown using 659 * the US date format convention but that may change depending on the 660 * local settings): 661 * <ul> 662 * <li>10:15am</li> 663 * <li>3:00pm - 4:00pm</li> 664 * <li>3pm - 4pm</li> 665 * <li>3PM - 4PM</li> 666 * <li>08:00 - 17:00</li> 667 * <li>Oct 9</li> 668 * <li>Tue, Oct 9</li> 669 * <li>October 9, 2007</li> 670 * <li>Oct 9 - 10</li> 671 * <li>Oct 9 - 10, 2007</li> 672 * <li>Oct 28 - Nov 3, 2007</li> 673 * <li>Dec 31, 2007 - Jan 1, 2008</li> 674 * <li>Oct 9, 8:00am - Oct 10, 5:00pm</li> 675 * <li>12/31/2007 - 01/01/2008</li> 676 * </ul> 677 * 678 * <p> 679 * The flags argument is a bitmask of options from the following list: 680 * 681 * <ul> 682 * <li>FORMAT_SHOW_TIME</li> 683 * <li>FORMAT_SHOW_WEEKDAY</li> 684 * <li>FORMAT_SHOW_YEAR</li> 685 * <li>FORMAT_SHOW_DATE</li> 686 * <li>FORMAT_NO_MONTH_DAY</li> 687 * <li>FORMAT_12HOUR</li> 688 * <li>FORMAT_24HOUR</li> 689 * <li>FORMAT_CAP_AMPM</li> 690 * <li>FORMAT_NO_NOON</li> 691 * <li>FORMAT_CAP_NOON</li> 692 * <li>FORMAT_NO_MIDNIGHT</li> 693 * <li>FORMAT_CAP_MIDNIGHT</li> 694 * <li>FORMAT_UTC</li> 695 * <li>FORMAT_ABBREV_TIME</li> 696 * <li>FORMAT_ABBREV_WEEKDAY</li> 697 * <li>FORMAT_ABBREV_MONTH</li> 698 * <li>FORMAT_ABBREV_ALL</li> 699 * <li>FORMAT_NUMERIC_DATE</li> 700 * </ul> 701 * 702 * <p> 703 * If FORMAT_SHOW_TIME is set, the time is shown as part of the date range. 704 * If the start and end time are the same, then just the start time is 705 * shown. 706 * 707 * <p> 708 * If FORMAT_SHOW_WEEKDAY is set, then the weekday is shown. 709 * 710 * <p> 711 * If FORMAT_SHOW_YEAR is set, then the year is always shown. 712 * If FORMAT_SHOW_YEAR is not set, then the year 713 * is shown only if it is different from the current year, or if the start 714 * and end dates fall on different years. 715 * 716 * <p> 717 * Normally the date is shown unless the start and end day are the same. 718 * If FORMAT_SHOW_DATE is set, then the date is always shown, even for 719 * same day ranges. 720 * 721 * <p> 722 * If FORMAT_NO_MONTH_DAY is set, then if the date is shown, just the 723 * month name will be shown, not the day of the month. For example, 724 * "January, 2008" instead of "January 6 - 12, 2008". 725 * 726 * <p> 727 * If FORMAT_CAP_AMPM is set and 12-hour time is used, then the "AM" 728 * and "PM" are capitalized. You should not use this flag 729 * because in some locales these terms cannot be capitalized, and in 730 * many others it doesn't make sense to do so even though it is possible. 731 * 732 * <p> 733 * If FORMAT_NO_NOON is set and 12-hour time is used, then "12pm" is 734 * shown instead of "noon". 735 * 736 * <p> 737 * If FORMAT_CAP_NOON is set and 12-hour time is used, then "Noon" is 738 * shown instead of "noon". You should probably not use this flag 739 * because in many locales it will not make sense to capitalize 740 * the term. 741 * 742 * <p> 743 * If FORMAT_NO_MIDNIGHT is set and 12-hour time is used, then "12am" is 744 * shown instead of "midnight". 745 * 746 * <p> 747 * If FORMAT_CAP_MIDNIGHT is set and 12-hour time is used, then "Midnight" 748 * is shown instead of "midnight". You should probably not use this 749 * flag because in many locales it will not make sense to capitalize 750 * the term. 751 * 752 * <p> 753 * If FORMAT_12HOUR is set and the time is shown, then the time is 754 * shown in the 12-hour time format. You should not normally set this. 755 * Instead, let the time format be chosen automatically according to the 756 * system settings. If both FORMAT_12HOUR and FORMAT_24HOUR are set, then 757 * FORMAT_24HOUR takes precedence. 758 * 759 * <p> 760 * If FORMAT_24HOUR is set and the time is shown, then the time is 761 * shown in the 24-hour time format. You should not normally set this. 762 * Instead, let the time format be chosen automatically according to the 763 * system settings. If both FORMAT_12HOUR and FORMAT_24HOUR are set, then 764 * FORMAT_24HOUR takes precedence. 765 * 766 * <p> 767 * If FORMAT_UTC is set, then the UTC time zone is used for the start 768 * and end milliseconds unless a time zone is specified. If a time zone 769 * is specified it will be used regardless of the FORMAT_UTC flag. 770 * 771 * <p> 772 * If FORMAT_ABBREV_TIME is set and 12-hour time format is used, then the 773 * start and end times (if shown) are abbreviated by not showing the minutes 774 * if they are zero. For example, instead of "3:00pm" the time would be 775 * abbreviated to "3pm". 776 * 777 * <p> 778 * If FORMAT_ABBREV_WEEKDAY is set, then the weekday (if shown) is 779 * abbreviated to a 3-letter string. 780 * 781 * <p> 782 * If FORMAT_ABBREV_MONTH is set, then the month (if shown) is abbreviated 783 * to a 3-letter string. 784 * 785 * <p> 786 * If FORMAT_ABBREV_ALL is set, then the weekday and the month (if shown) 787 * are abbreviated to 3-letter strings. 788 * 789 * <p> 790 * If FORMAT_NUMERIC_DATE is set, then the date is shown in numeric format 791 * instead of using the name of the month. For example, "12/31/2008" 792 * instead of "December 31, 2008". 793 * 794 * <p> 795 * If the end date ends at 12:00am at the beginning of a day, it is 796 * formatted as the end of the previous day in two scenarios: 797 * <ul> 798 * <li>For single day events. This results in "8pm - midnight" instead of 799 * "Nov 10, 8pm - Nov 11, 12am".</li> 800 * <li>When the time is not displayed. This results in "Nov 10 - 11" for 801 * an event with a start date of Nov 10 and an end date of Nov 12 at 802 * 00:00.</li> 803 * </ul> 804 * 805 * @param context the context is required only if the time is shown 806 * @param formatter the Formatter used for formatting the date range. 807 * Note: be sure to call setLength(0) on StringBuilder passed to 808 * the Formatter constructor unless you want the results to accumulate. 809 * @param startMillis the start time in UTC milliseconds 810 * @param endMillis the end time in UTC milliseconds 811 * @param flags a bit mask of options 812 * @param timeZone the time zone to compute the string in. Use null for local 813 * or if the FORMAT_UTC flag is being used. 814 * 815 * @return the formatter with the formatted date/time range appended to the string buffer. 816 */ formatDateRange(Context context, Formatter formatter, long startMillis, long endMillis, int flags, String timeZone)817 public static Formatter formatDateRange(Context context, Formatter formatter, long startMillis, 818 long endMillis, int flags, String timeZone) { 819 // If we're being asked to format a time without being explicitly told whether to use 820 // the 12- or 24-hour clock, icu4c will fall back to the locale's preferred 12/24 format, 821 // but we want to fall back to the user's preference. 822 if ((flags & (FORMAT_SHOW_TIME | FORMAT_12HOUR | FORMAT_24HOUR)) == FORMAT_SHOW_TIME) { 823 flags |= DateFormat.is24HourFormat(context) ? FORMAT_24HOUR : FORMAT_12HOUR; 824 } 825 826 String range = DateIntervalFormat.formatDateRange(startMillis, endMillis, flags, timeZone); 827 try { 828 formatter.out().append(range); 829 } catch (IOException impossible) { 830 throw new AssertionError(impossible); 831 } 832 return formatter; 833 } 834 835 /** 836 * Formats a date or a time according to the local conventions. There are 837 * lots of options that allow the caller to control, for example, if the 838 * time is shown, if the day of the week is shown, if the month name is 839 * abbreviated, if noon is shown instead of 12pm, and so on. For the 840 * complete list of options, see the documentation for 841 * {@link #formatDateRange}. 842 * <p> 843 * Example output strings (date formats in these examples are shown using 844 * the US date format convention but that may change depending on the 845 * local settings): 846 * <ul> 847 * <li>10:15am</li> 848 * <li>3:00pm</li> 849 * <li>3pm</li> 850 * <li>3PM</li> 851 * <li>08:00</li> 852 * <li>17:00</li> 853 * <li>noon</li> 854 * <li>Noon</li> 855 * <li>midnight</li> 856 * <li>Midnight</li> 857 * <li>Oct 31</li> 858 * <li>Oct 31, 2007</li> 859 * <li>October 31, 2007</li> 860 * <li>10am, Oct 31</li> 861 * <li>17:00, Oct 31</li> 862 * <li>Wed</li> 863 * <li>Wednesday</li> 864 * <li>10am, Wed, Oct 31</li> 865 * <li>Wed, Oct 31</li> 866 * <li>Wednesday, Oct 31</li> 867 * <li>Wed, Oct 31, 2007</li> 868 * <li>Wed, October 31</li> 869 * <li>10/31/2007</li> 870 * </ul> 871 * 872 * @param context the context is required only if the time is shown 873 * @param millis a point in time in UTC milliseconds 874 * @param flags a bit mask of formatting options 875 * @return a string containing the formatted date/time. 876 */ formatDateTime(Context context, long millis, int flags)877 public static String formatDateTime(Context context, long millis, int flags) { 878 return formatDateRange(context, millis, millis, flags); 879 } 880 881 /** 882 * @return a relative time string to display the time expressed by millis. Times 883 * are counted starting at midnight, which means that assuming that the current 884 * time is March 31st, 0:30: 885 * <ul> 886 * <li>"millis=0:10 today" will be displayed as "0:10"</li> 887 * <li>"millis=11:30pm the day before" will be displayed as "Mar 30"</li> 888 * </ul> 889 * If the given millis is in a different year, then the full date is 890 * returned in numeric format (e.g., "10/12/2008"). 891 * 892 * @param withPreposition If true, the string returned will include the correct 893 * preposition ("at 9:20am", "on 10/12/2008" or "on May 29"). 894 */ getRelativeTimeSpanString(Context c, long millis, boolean withPreposition)895 public static CharSequence getRelativeTimeSpanString(Context c, long millis, 896 boolean withPreposition) { 897 898 String result; 899 long now = System.currentTimeMillis(); 900 long span = Math.abs(now - millis); 901 902 synchronized (DateUtils.class) { 903 if (sNowTime == null) { 904 sNowTime = new Time(); 905 } 906 907 if (sThenTime == null) { 908 sThenTime = new Time(); 909 } 910 911 sNowTime.set(now); 912 sThenTime.set(millis); 913 914 int prepositionId; 915 if (span < DAY_IN_MILLIS && sNowTime.weekDay == sThenTime.weekDay) { 916 // Same day 917 int flags = FORMAT_SHOW_TIME; 918 result = formatDateRange(c, millis, millis, flags); 919 prepositionId = R.string.preposition_for_time; 920 } else if (sNowTime.year != sThenTime.year) { 921 // Different years 922 int flags = FORMAT_SHOW_DATE | FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE; 923 result = formatDateRange(c, millis, millis, flags); 924 925 // This is a date (like "10/31/2008" so use the date preposition) 926 prepositionId = R.string.preposition_for_date; 927 } else { 928 // Default 929 int flags = FORMAT_SHOW_DATE | FORMAT_ABBREV_MONTH; 930 result = formatDateRange(c, millis, millis, flags); 931 prepositionId = R.string.preposition_for_date; 932 } 933 if (withPreposition) { 934 Resources res = c.getResources(); 935 result = res.getString(prepositionId, result); 936 } 937 } 938 return result; 939 } 940 941 /** 942 * Convenience function to return relative time string without preposition. 943 * @param c context for resources 944 * @param millis time in milliseconds 945 * @return {@link CharSequence} containing relative time. 946 * @see #getRelativeTimeSpanString(Context, long, boolean) 947 */ getRelativeTimeSpanString(Context c, long millis)948 public static CharSequence getRelativeTimeSpanString(Context c, long millis) { 949 return getRelativeTimeSpanString(c, millis, false /* no preposition */); 950 } 951 952 private static Time sNowTime; 953 private static Time sThenTime; 954 } 955