DateUtils.java revision d3c01012d8d329e541214e8207272ab5cc9ca004
1/*
2 * Copyright (C) 2006 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.text.format;
18
19import com.android.internal.R;
20
21import android.content.Context;
22import android.content.res.Configuration;
23import android.content.res.Resources;
24
25import java.util.Calendar;
26import java.util.Date;
27import java.util.Formatter;
28import java.util.GregorianCalendar;
29import java.util.Locale;
30import java.util.TimeZone;
31
32/**
33 * This class contains various date-related utilities for creating text for things like
34 * elapsed time and date ranges, strings for days of the week and months, and AM/PM text etc.
35 */
36public class DateUtils
37{
38    private static final Object sLock = new Object();
39    private static final int[] sDaysLong = new int[] {
40            com.android.internal.R.string.day_of_week_long_sunday,
41            com.android.internal.R.string.day_of_week_long_monday,
42            com.android.internal.R.string.day_of_week_long_tuesday,
43            com.android.internal.R.string.day_of_week_long_wednesday,
44            com.android.internal.R.string.day_of_week_long_thursday,
45            com.android.internal.R.string.day_of_week_long_friday,
46            com.android.internal.R.string.day_of_week_long_saturday,
47        };
48    private static final int[] sDaysMedium = new int[] {
49            com.android.internal.R.string.day_of_week_medium_sunday,
50            com.android.internal.R.string.day_of_week_medium_monday,
51            com.android.internal.R.string.day_of_week_medium_tuesday,
52            com.android.internal.R.string.day_of_week_medium_wednesday,
53            com.android.internal.R.string.day_of_week_medium_thursday,
54            com.android.internal.R.string.day_of_week_medium_friday,
55            com.android.internal.R.string.day_of_week_medium_saturday,
56        };
57    private static final int[] sDaysShort = new int[] {
58            com.android.internal.R.string.day_of_week_short_sunday,
59            com.android.internal.R.string.day_of_week_short_monday,
60            com.android.internal.R.string.day_of_week_short_tuesday,
61            com.android.internal.R.string.day_of_week_short_wednesday,
62            com.android.internal.R.string.day_of_week_short_thursday,
63            com.android.internal.R.string.day_of_week_short_friday,
64            com.android.internal.R.string.day_of_week_short_saturday,
65        };
66    private static final int[] sDaysShortest = new int[] {
67            com.android.internal.R.string.day_of_week_shortest_sunday,
68            com.android.internal.R.string.day_of_week_shortest_monday,
69            com.android.internal.R.string.day_of_week_shortest_tuesday,
70            com.android.internal.R.string.day_of_week_shortest_wednesday,
71            com.android.internal.R.string.day_of_week_shortest_thursday,
72            com.android.internal.R.string.day_of_week_shortest_friday,
73            com.android.internal.R.string.day_of_week_shortest_saturday,
74        };
75    private static final int[] sMonthsStandaloneLong = new int [] {
76            com.android.internal.R.string.month_long_standalone_january,
77            com.android.internal.R.string.month_long_standalone_february,
78            com.android.internal.R.string.month_long_standalone_march,
79            com.android.internal.R.string.month_long_standalone_april,
80            com.android.internal.R.string.month_long_standalone_may,
81            com.android.internal.R.string.month_long_standalone_june,
82            com.android.internal.R.string.month_long_standalone_july,
83            com.android.internal.R.string.month_long_standalone_august,
84            com.android.internal.R.string.month_long_standalone_september,
85            com.android.internal.R.string.month_long_standalone_october,
86            com.android.internal.R.string.month_long_standalone_november,
87            com.android.internal.R.string.month_long_standalone_december,
88        };
89    private static final int[] sMonthsLong = new int [] {
90            com.android.internal.R.string.month_long_january,
91            com.android.internal.R.string.month_long_february,
92            com.android.internal.R.string.month_long_march,
93            com.android.internal.R.string.month_long_april,
94            com.android.internal.R.string.month_long_may,
95            com.android.internal.R.string.month_long_june,
96            com.android.internal.R.string.month_long_july,
97            com.android.internal.R.string.month_long_august,
98            com.android.internal.R.string.month_long_september,
99            com.android.internal.R.string.month_long_october,
100            com.android.internal.R.string.month_long_november,
101            com.android.internal.R.string.month_long_december,
102        };
103    private static final int[] sMonthsMedium = new int [] {
104            com.android.internal.R.string.month_medium_january,
105            com.android.internal.R.string.month_medium_february,
106            com.android.internal.R.string.month_medium_march,
107            com.android.internal.R.string.month_medium_april,
108            com.android.internal.R.string.month_medium_may,
109            com.android.internal.R.string.month_medium_june,
110            com.android.internal.R.string.month_medium_july,
111            com.android.internal.R.string.month_medium_august,
112            com.android.internal.R.string.month_medium_september,
113            com.android.internal.R.string.month_medium_october,
114            com.android.internal.R.string.month_medium_november,
115            com.android.internal.R.string.month_medium_december,
116        };
117    private static final int[] sMonthsShortest = new int [] {
118            com.android.internal.R.string.month_shortest_january,
119            com.android.internal.R.string.month_shortest_february,
120            com.android.internal.R.string.month_shortest_march,
121            com.android.internal.R.string.month_shortest_april,
122            com.android.internal.R.string.month_shortest_may,
123            com.android.internal.R.string.month_shortest_june,
124            com.android.internal.R.string.month_shortest_july,
125            com.android.internal.R.string.month_shortest_august,
126            com.android.internal.R.string.month_shortest_september,
127            com.android.internal.R.string.month_shortest_october,
128            com.android.internal.R.string.month_shortest_november,
129            com.android.internal.R.string.month_shortest_december,
130        };
131    private static final int[] sAmPm = new int[] {
132            com.android.internal.R.string.am,
133            com.android.internal.R.string.pm,
134        };
135    private static Configuration sLastConfig;
136    private static java.text.DateFormat sStatusTimeFormat;
137    private static String sElapsedFormatMMSS;
138    private static String sElapsedFormatHMMSS;
139
140    private static final String FAST_FORMAT_HMMSS = "%1$d:%2$02d:%3$02d";
141    private static final String FAST_FORMAT_MMSS = "%1$02d:%2$02d";
142    private static final char TIME_PADDING = '0';
143    private static final char TIME_SEPARATOR = ':';
144
145
146    public static final long SECOND_IN_MILLIS = 1000;
147    public static final long MINUTE_IN_MILLIS = SECOND_IN_MILLIS * 60;
148    public static final long HOUR_IN_MILLIS = MINUTE_IN_MILLIS * 60;
149    public static final long DAY_IN_MILLIS = HOUR_IN_MILLIS * 24;
150    public static final long WEEK_IN_MILLIS = DAY_IN_MILLIS * 7;
151    /**
152     * This constant is actually the length of 364 days, not of a year!
153     */
154    public static final long YEAR_IN_MILLIS = WEEK_IN_MILLIS * 52;
155
156    // The following FORMAT_* symbols are used for specifying the format of
157    // dates and times in the formatDateRange method.
158    public static final int FORMAT_SHOW_TIME = 0x00001;
159    public static final int FORMAT_SHOW_WEEKDAY = 0x00002;
160    public static final int FORMAT_SHOW_YEAR = 0x00004;
161    public static final int FORMAT_NO_YEAR = 0x00008;
162    public static final int FORMAT_SHOW_DATE = 0x00010;
163    public static final int FORMAT_NO_MONTH_DAY = 0x00020;
164    @Deprecated
165    public static final int FORMAT_12HOUR = 0x00040;
166    @Deprecated
167    public static final int FORMAT_24HOUR = 0x00080;
168    @Deprecated
169    public static final int FORMAT_CAP_AMPM = 0x00100;
170    public static final int FORMAT_NO_NOON = 0x00200;
171    @Deprecated
172    public static final int FORMAT_CAP_NOON = 0x00400;
173    public static final int FORMAT_NO_MIDNIGHT = 0x00800;
174    @Deprecated
175    public static final int FORMAT_CAP_MIDNIGHT = 0x01000;
176    /**
177     * @deprecated Use
178     * {@link #formatDateRange(Context, Formatter, long, long, int, String) formatDateRange}
179     * and pass in {@link Time#TIMEZONE_UTC Time.TIMEZONE_UTC} for the timeZone instead.
180     */
181    @Deprecated
182    public static final int FORMAT_UTC = 0x02000;
183    public static final int FORMAT_ABBREV_TIME = 0x04000;
184    public static final int FORMAT_ABBREV_WEEKDAY = 0x08000;
185    public static final int FORMAT_ABBREV_MONTH = 0x10000;
186    public static final int FORMAT_NUMERIC_DATE = 0x20000;
187    public static final int FORMAT_ABBREV_RELATIVE = 0x40000;
188    public static final int FORMAT_ABBREV_ALL = 0x80000;
189    @Deprecated
190    public static final int FORMAT_CAP_NOON_MIDNIGHT = (FORMAT_CAP_NOON | FORMAT_CAP_MIDNIGHT);
191    @Deprecated
192    public static final int FORMAT_NO_NOON_MIDNIGHT = (FORMAT_NO_NOON | FORMAT_NO_MIDNIGHT);
193
194    // Date and time format strings that are constant and don't need to be
195    // translated.
196    /**
197     * This is not actually the preferred 24-hour date format in all locales.
198     * @deprecated use {@link java.text.SimpleDateFormat} instead.
199     */
200    @Deprecated
201    public static final String HOUR_MINUTE_24 = "%H:%M";
202    public static final String MONTH_FORMAT = "%B";
203    /**
204     * This is not actually a useful month name in all locales.
205     * @deprecated use {@link java.text.SimpleDateFormat} instead.
206     */
207    @Deprecated
208    public static final String ABBREV_MONTH_FORMAT = "%b";
209    public static final String NUMERIC_MONTH_FORMAT = "%m";
210    public static final String MONTH_DAY_FORMAT = "%-d";
211    public static final String YEAR_FORMAT = "%Y";
212    public static final String YEAR_FORMAT_TWO_DIGITS = "%g";
213    public static final String WEEKDAY_FORMAT = "%A";
214    public static final String ABBREV_WEEKDAY_FORMAT = "%a";
215
216    // This table is used to lookup the resource string id of a format string
217    // used for formatting a start and end date that fall in the same year.
218    // The index is constructed from a bit-wise OR of the boolean values:
219    // {showTime, showYear, showWeekDay}.  For example, if showYear and
220    // showWeekDay are both true, then the index would be 3.
221    /** @deprecated do not use. */
222    public static final int sameYearTable[] = {
223        com.android.internal.R.string.same_year_md1_md2,
224        com.android.internal.R.string.same_year_wday1_md1_wday2_md2,
225        com.android.internal.R.string.same_year_mdy1_mdy2,
226        com.android.internal.R.string.same_year_wday1_mdy1_wday2_mdy2,
227        com.android.internal.R.string.same_year_md1_time1_md2_time2,
228        com.android.internal.R.string.same_year_wday1_md1_time1_wday2_md2_time2,
229        com.android.internal.R.string.same_year_mdy1_time1_mdy2_time2,
230        com.android.internal.R.string.same_year_wday1_mdy1_time1_wday2_mdy2_time2,
231
232        // Numeric date strings
233        com.android.internal.R.string.numeric_md1_md2,
234        com.android.internal.R.string.numeric_wday1_md1_wday2_md2,
235        com.android.internal.R.string.numeric_mdy1_mdy2,
236        com.android.internal.R.string.numeric_wday1_mdy1_wday2_mdy2,
237        com.android.internal.R.string.numeric_md1_time1_md2_time2,
238        com.android.internal.R.string.numeric_wday1_md1_time1_wday2_md2_time2,
239        com.android.internal.R.string.numeric_mdy1_time1_mdy2_time2,
240        com.android.internal.R.string.numeric_wday1_mdy1_time1_wday2_mdy2_time2,
241    };
242
243    // This table is used to lookup the resource string id of a format string
244    // used for formatting a start and end date that fall in the same month.
245    // The index is constructed from a bit-wise OR of the boolean values:
246    // {showTime, showYear, showWeekDay}.  For example, if showYear and
247    // showWeekDay are both true, then the index would be 3.
248    /** @deprecated do not use. */
249    public static final int sameMonthTable[] = {
250        com.android.internal.R.string.same_month_md1_md2,
251        com.android.internal.R.string.same_month_wday1_md1_wday2_md2,
252        com.android.internal.R.string.same_month_mdy1_mdy2,
253        com.android.internal.R.string.same_month_wday1_mdy1_wday2_mdy2,
254        com.android.internal.R.string.same_month_md1_time1_md2_time2,
255        com.android.internal.R.string.same_month_wday1_md1_time1_wday2_md2_time2,
256        com.android.internal.R.string.same_month_mdy1_time1_mdy2_time2,
257        com.android.internal.R.string.same_month_wday1_mdy1_time1_wday2_mdy2_time2,
258
259        com.android.internal.R.string.numeric_md1_md2,
260        com.android.internal.R.string.numeric_wday1_md1_wday2_md2,
261        com.android.internal.R.string.numeric_mdy1_mdy2,
262        com.android.internal.R.string.numeric_wday1_mdy1_wday2_mdy2,
263        com.android.internal.R.string.numeric_md1_time1_md2_time2,
264        com.android.internal.R.string.numeric_wday1_md1_time1_wday2_md2_time2,
265        com.android.internal.R.string.numeric_mdy1_time1_mdy2_time2,
266        com.android.internal.R.string.numeric_wday1_mdy1_time1_wday2_mdy2_time2,
267    };
268
269    /**
270     * Request the full spelled-out name. For use with the 'abbrev' parameter of
271     * {@link #getDayOfWeekString} and {@link #getMonthString}.
272     *
273     * @more <p>
274     *       e.g. "Sunday" or "January"
275     * @deprecated use {@link java.text.SimpleDateFormat} instead.
276     */
277    @Deprecated
278    public static final int LENGTH_LONG = 10;
279
280    /**
281     * Request an abbreviated version of the name. For use with the 'abbrev'
282     * parameter of {@link #getDayOfWeekString} and {@link #getMonthString}.
283     *
284     * @more <p>
285     *       e.g. "Sun" or "Jan"
286     * @deprecated use {@link java.text.SimpleDateFormat} instead.
287     */
288    @Deprecated
289    public static final int LENGTH_MEDIUM = 20;
290
291    /**
292     * Request a shorter abbreviated version of the name.
293     * For use with the 'abbrev' parameter of {@link #getDayOfWeekString} and {@link #getMonthString}.
294     * @more
295     * <p>e.g. "Su" or "Jan"
296     * <p>In most languages, the results returned for LENGTH_SHORT will be the same as
297     * the results returned for {@link #LENGTH_MEDIUM}.
298     * @deprecated use {@link java.text.SimpleDateFormat} instead.
299     */
300    @Deprecated
301    public static final int LENGTH_SHORT = 30;
302
303    /**
304     * Request an even shorter abbreviated version of the name.
305     * Do not use this.  Currently this will always return the same result
306     * as {@link #LENGTH_SHORT}.
307     * @deprecated use {@link java.text.SimpleDateFormat} instead.
308     */
309    @Deprecated
310    public static final int LENGTH_SHORTER = 40;
311
312    /**
313     * Request an even shorter abbreviated version of the name.
314     * For use with the 'abbrev' parameter of {@link #getDayOfWeekString} and {@link #getMonthString}.
315     * @more
316     * <p>e.g. "S", "T", "T" or "J"
317     * <p>In some languages, the results returned for LENGTH_SHORTEST will be the same as
318     * the results returned for {@link #LENGTH_SHORT}.
319     * @deprecated use {@link java.text.SimpleDateFormat} instead.
320     */
321    @Deprecated
322    public static final int LENGTH_SHORTEST = 50;
323
324    /**
325     * Return a string for the day of the week.
326     * @param dayOfWeek One of {@link Calendar#SUNDAY Calendar.SUNDAY},
327     *               {@link Calendar#MONDAY Calendar.MONDAY}, etc.
328     * @param abbrev One of {@link #LENGTH_LONG}, {@link #LENGTH_SHORT},
329     *               {@link #LENGTH_MEDIUM}, or {@link #LENGTH_SHORTEST}.
330     *               Note that in most languages, {@link #LENGTH_SHORT}
331     *               will return the same as {@link #LENGTH_MEDIUM}.
332     *               Undefined lengths will return {@link #LENGTH_MEDIUM}
333     *               but may return something different in the future.
334     * @throws IndexOutOfBoundsException if the dayOfWeek is out of bounds.
335     * @deprecated use {@link java.text.SimpleDateFormat} instead.
336     */
337    @Deprecated
338    public static String getDayOfWeekString(int dayOfWeek, int abbrev) {
339        int[] list;
340        switch (abbrev) {
341            case LENGTH_LONG:       list = sDaysLong;       break;
342            case LENGTH_MEDIUM:     list = sDaysMedium;     break;
343            case LENGTH_SHORT:      list = sDaysShort;      break;
344            case LENGTH_SHORTER:    list = sDaysShort;      break;
345            case LENGTH_SHORTEST:   list = sDaysShortest;   break;
346            default:                list = sDaysMedium;     break;
347        }
348
349        Resources r = Resources.getSystem();
350        return r.getString(list[dayOfWeek - Calendar.SUNDAY]);
351    }
352
353    /**
354     * Return a localized string for AM or PM.
355     * @param ampm Either {@link Calendar#AM Calendar.AM} or {@link Calendar#PM Calendar.PM}.
356     * @throws IndexOutOfBoundsException if the ampm is out of bounds.
357     * @return Localized version of "AM" or "PM".
358     * @deprecated use {@link java.text.SimpleDateFormat} instead.
359     */
360    @Deprecated
361    public static String getAMPMString(int ampm) {
362        Resources r = Resources.getSystem();
363        return r.getString(sAmPm[ampm - Calendar.AM]);
364    }
365
366    /**
367     * Return a localized string for the month of the year.
368     * @param month One of {@link Calendar#JANUARY Calendar.JANUARY},
369     *               {@link Calendar#FEBRUARY Calendar.FEBRUARY}, etc.
370     * @param abbrev One of {@link #LENGTH_LONG}, {@link #LENGTH_MEDIUM},
371     *               or {@link #LENGTH_SHORTEST}.
372     *               Undefined lengths will return {@link #LENGTH_MEDIUM}
373     *               but may return something different in the future.
374     * @return Localized month of the year.
375     * @deprecated use {@link java.text.SimpleDateFormat} instead.
376     */
377    @Deprecated
378    public static String getMonthString(int month, int abbrev) {
379        // Note that here we use sMonthsMedium for MEDIUM, SHORT and SHORTER.
380        // This is a shortcut to not spam the translators with too many variations
381        // of the same string.  If we find that in a language the distinction
382        // is necessary, we can can add more without changing this API.
383        int[] list;
384        switch (abbrev) {
385            case LENGTH_LONG:       list = sMonthsLong;     break;
386            case LENGTH_MEDIUM:     list = sMonthsMedium;   break;
387            case LENGTH_SHORT:      list = sMonthsMedium;   break;
388            case LENGTH_SHORTER:    list = sMonthsMedium;   break;
389            case LENGTH_SHORTEST:   list = sMonthsShortest; break;
390            default:                list = sMonthsMedium;   break;
391        }
392
393        Resources r = Resources.getSystem();
394        return r.getString(list[month - Calendar.JANUARY]);
395    }
396
397    /**
398     * Return a localized string for the month of the year, for
399     * contexts where the month is not formatted together with
400     * a day of the month.
401     *
402     * @param month One of {@link Calendar#JANUARY Calendar.JANUARY},
403     *               {@link Calendar#FEBRUARY Calendar.FEBRUARY}, etc.
404     * @param abbrev One of {@link #LENGTH_LONG}, {@link #LENGTH_MEDIUM},
405     *               or {@link #LENGTH_SHORTEST}.
406     *               Undefined lengths will return {@link #LENGTH_MEDIUM}
407     *               but may return something different in the future.
408     * @return Localized month of the year.
409     * @hide Pending API council approval
410     * @deprecated use {@link java.text.SimpleDateFormat} instead.
411     */
412    @Deprecated
413    public static String getStandaloneMonthString(int month, int abbrev) {
414        // Note that here we use sMonthsMedium for MEDIUM, SHORT and SHORTER.
415        // This is a shortcut to not spam the translators with too many variations
416        // of the same string.  If we find that in a language the distinction
417        // is necessary, we can can add more without changing this API.
418        int[] list;
419        switch (abbrev) {
420            case LENGTH_LONG:       list = sMonthsStandaloneLong;
421                                                            break;
422            case LENGTH_MEDIUM:     list = sMonthsMedium;   break;
423            case LENGTH_SHORT:      list = sMonthsMedium;   break;
424            case LENGTH_SHORTER:    list = sMonthsMedium;   break;
425            case LENGTH_SHORTEST:   list = sMonthsShortest; break;
426            default:                list = sMonthsMedium;   break;
427        }
428
429        Resources r = Resources.getSystem();
430        return r.getString(list[month - Calendar.JANUARY]);
431    }
432
433    /**
434     * Returns a string describing the elapsed time since startTime.
435     * @param startTime some time in the past.
436     * @return a String object containing the elapsed time.
437     * @see #getRelativeTimeSpanString(long, long, long)
438     */
439    public static CharSequence getRelativeTimeSpanString(long startTime) {
440        return getRelativeTimeSpanString(startTime, System.currentTimeMillis(), MINUTE_IN_MILLIS);
441    }
442
443    /**
444     * Returns a string describing 'time' as a time relative to 'now'.
445     * <p>
446     * Time spans in the past are formatted like "42 minutes ago".
447     * Time spans in the future are formatted like "in 42 minutes".
448     *
449     * @param time the time to describe, in milliseconds
450     * @param now the current time in milliseconds
451     * @param minResolution the minimum timespan to report. For example, a time 3 seconds in the
452     *     past will be reported as "0 minutes ago" if this is set to MINUTE_IN_MILLIS. Pass one of
453     *     0, MINUTE_IN_MILLIS, HOUR_IN_MILLIS, DAY_IN_MILLIS, WEEK_IN_MILLIS
454     */
455    public static CharSequence getRelativeTimeSpanString(long time, long now, long minResolution) {
456        int flags = FORMAT_SHOW_DATE | FORMAT_SHOW_YEAR | FORMAT_ABBREV_MONTH;
457        return getRelativeTimeSpanString(time, now, minResolution, flags);
458    }
459
460    /**
461     * Returns a string describing 'time' as a time relative to 'now'.
462     * <p>
463     * Time spans in the past are formatted like "42 minutes ago". Time spans in
464     * the future are formatted like "in 42 minutes".
465     * <p>
466     * Can use {@link #FORMAT_ABBREV_RELATIVE} flag to use abbreviated relative
467     * times, like "42 mins ago".
468     *
469     * @param time the time to describe, in milliseconds
470     * @param now the current time in milliseconds
471     * @param minResolution the minimum timespan to report. For example, a time
472     *            3 seconds in the past will be reported as "0 minutes ago" if
473     *            this is set to MINUTE_IN_MILLIS. Pass one of 0,
474     *            MINUTE_IN_MILLIS, HOUR_IN_MILLIS, DAY_IN_MILLIS,
475     *            WEEK_IN_MILLIS
476     * @param flags a bit mask of formatting options, such as
477     *            {@link #FORMAT_NUMERIC_DATE} or
478     *            {@link #FORMAT_ABBREV_RELATIVE}
479     */
480    public static CharSequence getRelativeTimeSpanString(long time, long now, long minResolution,
481            int flags) {
482        Resources r = Resources.getSystem();
483        boolean abbrevRelative = (flags & (FORMAT_ABBREV_RELATIVE | FORMAT_ABBREV_ALL)) != 0;
484
485        boolean past = (now >= time);
486        long duration = Math.abs(now - time);
487
488        int resId;
489        long count;
490        if (duration < MINUTE_IN_MILLIS && minResolution < MINUTE_IN_MILLIS) {
491            count = duration / SECOND_IN_MILLIS;
492            if (past) {
493                if (abbrevRelative) {
494                    resId = com.android.internal.R.plurals.abbrev_num_seconds_ago;
495                } else {
496                    resId = com.android.internal.R.plurals.num_seconds_ago;
497                }
498            } else {
499                if (abbrevRelative) {
500                    resId = com.android.internal.R.plurals.abbrev_in_num_seconds;
501                } else {
502                    resId = com.android.internal.R.plurals.in_num_seconds;
503                }
504            }
505        } else if (duration < HOUR_IN_MILLIS && minResolution < HOUR_IN_MILLIS) {
506            count = duration / MINUTE_IN_MILLIS;
507            if (past) {
508                if (abbrevRelative) {
509                    resId = com.android.internal.R.plurals.abbrev_num_minutes_ago;
510                } else {
511                    resId = com.android.internal.R.plurals.num_minutes_ago;
512                }
513            } else {
514                if (abbrevRelative) {
515                    resId = com.android.internal.R.plurals.abbrev_in_num_minutes;
516                } else {
517                    resId = com.android.internal.R.plurals.in_num_minutes;
518                }
519            }
520        } else if (duration < DAY_IN_MILLIS && minResolution < DAY_IN_MILLIS) {
521            count = duration / HOUR_IN_MILLIS;
522            if (past) {
523                if (abbrevRelative) {
524                    resId = com.android.internal.R.plurals.abbrev_num_hours_ago;
525                } else {
526                    resId = com.android.internal.R.plurals.num_hours_ago;
527                }
528            } else {
529                if (abbrevRelative) {
530                    resId = com.android.internal.R.plurals.abbrev_in_num_hours;
531                } else {
532                    resId = com.android.internal.R.plurals.in_num_hours;
533                }
534            }
535        } else if (duration < WEEK_IN_MILLIS && minResolution < WEEK_IN_MILLIS) {
536            count = getNumberOfDaysPassed(time, now);
537            if (past) {
538                if (abbrevRelative) {
539                    resId = com.android.internal.R.plurals.abbrev_num_days_ago;
540                } else {
541                    resId = com.android.internal.R.plurals.num_days_ago;
542                }
543            } else {
544                if (abbrevRelative) {
545                    resId = com.android.internal.R.plurals.abbrev_in_num_days;
546                } else {
547                    resId = com.android.internal.R.plurals.in_num_days;
548                }
549            }
550        } else {
551            // We know that we won't be showing the time, so it is safe to pass
552            // in a null context.
553            return formatDateRange(null, time, time, flags);
554        }
555
556        String format = r.getQuantityString(resId, (int) count);
557        return String.format(format, count);
558    }
559
560    /**
561     * Returns the number of days passed between two dates.
562     *
563     * @param date1 first date
564     * @param date2 second date
565     * @return number of days passed between to dates.
566     */
567    private synchronized static long getNumberOfDaysPassed(long date1, long date2) {
568        if (sThenTime == null) {
569            sThenTime = new Time();
570        }
571        sThenTime.set(date1);
572        int day1 = Time.getJulianDay(date1, sThenTime.gmtoff);
573        sThenTime.set(date2);
574        int day2 = Time.getJulianDay(date2, sThenTime.gmtoff);
575        return Math.abs(day2 - day1);
576    }
577
578    /**
579     * Return string describing the elapsed time since startTime formatted like
580     * "[relative time/date], [time]".
581     * <p>
582     * Example output strings for the US date format.
583     * <ul>
584     * <li>3 mins ago, 10:15 AM</li>
585     * <li>yesterday, 12:20 PM</li>
586     * <li>Dec 12, 4:12 AM</li>
587     * <li>11/14/2007, 8:20 AM</li>
588     * </ul>
589     *
590     * @param time some time in the past.
591     * @param minResolution the minimum elapsed time (in milliseconds) to report
592     *            when showing relative times. For example, a time 3 seconds in
593     *            the past will be reported as "0 minutes ago" if this is set to
594     *            {@link #MINUTE_IN_MILLIS}.
595     * @param transitionResolution the elapsed time (in milliseconds) at which
596     *            to stop reporting relative measurements. Elapsed times greater
597     *            than this resolution will default to normal date formatting.
598     *            For example, will transition from "6 days ago" to "Dec 12"
599     *            when using {@link #WEEK_IN_MILLIS}.
600     */
601    public static CharSequence getRelativeDateTimeString(Context c, long time, long minResolution,
602            long transitionResolution, int flags) {
603        Resources r = Resources.getSystem();
604
605        long now = System.currentTimeMillis();
606        long duration = Math.abs(now - time);
607
608        // getRelativeTimeSpanString() doesn't correctly format relative dates
609        // above a week or exact dates below a day, so clamp
610        // transitionResolution as needed.
611        if (transitionResolution > WEEK_IN_MILLIS) {
612            transitionResolution = WEEK_IN_MILLIS;
613        } else if (transitionResolution < DAY_IN_MILLIS) {
614            transitionResolution = DAY_IN_MILLIS;
615        }
616
617        CharSequence timeClause = formatDateRange(c, time, time, FORMAT_SHOW_TIME);
618
619        String result;
620        if (duration < transitionResolution) {
621            CharSequence relativeClause = getRelativeTimeSpanString(time, now, minResolution, flags);
622            result = r.getString(com.android.internal.R.string.relative_time, relativeClause, timeClause);
623        } else {
624            CharSequence dateClause = getRelativeTimeSpanString(c, time, false);
625            result = r.getString(com.android.internal.R.string.date_time, dateClause, timeClause);
626        }
627
628        return result;
629    }
630
631    /**
632     * Returns a string describing a day relative to the current day. For example if the day is
633     * today this function returns "Today", if the day was a week ago it returns "7 days ago", and
634     * if the day is in 2 weeks it returns "in 14 days".
635     *
636     * @param r the resources to get the strings from
637     * @param day the relative day to describe in UTC milliseconds
638     * @param today the current time in UTC milliseconds
639     * @return a formatting string
640     */
641    private static final String getRelativeDayString(Resources r, long day, long today) {
642        Time startTime = new Time();
643        startTime.set(day);
644        Time currentTime = new Time();
645        currentTime.set(today);
646
647        int startDay = Time.getJulianDay(day, startTime.gmtoff);
648        int currentDay = Time.getJulianDay(today, currentTime.gmtoff);
649
650        int days = Math.abs(currentDay - startDay);
651        boolean past = (today > day);
652
653        if (days == 1) {
654            if (past) {
655                return r.getString(com.android.internal.R.string.yesterday);
656            } else {
657                return r.getString(com.android.internal.R.string.tomorrow);
658            }
659        } else if (days == 0) {
660            return r.getString(com.android.internal.R.string.today);
661        }
662
663        int resId;
664        if (past) {
665            resId = com.android.internal.R.plurals.num_days_ago;
666        } else {
667            resId = com.android.internal.R.plurals.in_num_days;
668        }
669
670        String format = r.getQuantityString(resId, days);
671        return String.format(format, days);
672    }
673
674    private static void initFormatStrings() {
675        synchronized (sLock) {
676            initFormatStringsLocked();
677        }
678    }
679
680    private static void initFormatStringsLocked() {
681        Resources r = Resources.getSystem();
682        Configuration cfg = r.getConfiguration();
683        if (sLastConfig == null || !sLastConfig.equals(cfg)) {
684            sLastConfig = cfg;
685            sStatusTimeFormat = java.text.DateFormat.getTimeInstance(java.text.DateFormat.SHORT);
686            sElapsedFormatMMSS = r.getString(com.android.internal.R.string.elapsed_time_short_format_mm_ss);
687            sElapsedFormatHMMSS = r.getString(com.android.internal.R.string.elapsed_time_short_format_h_mm_ss);
688        }
689    }
690
691    /**
692     * Format a time so it appears like it would in the status bar clock.
693     * @deprecated use {@link #DateFormat.getTimeFormat(Context)} instead.
694     * @hide
695     */
696    public static final CharSequence timeString(long millis) {
697        synchronized (sLock) {
698            initFormatStringsLocked();
699            return sStatusTimeFormat.format(millis);
700        }
701    }
702
703    /**
704     * Formats an elapsed time in the form "MM:SS" or "H:MM:SS"
705     * for display on the call-in-progress screen.
706     * @param elapsedSeconds the elapsed time in seconds.
707     */
708    public static String formatElapsedTime(long elapsedSeconds) {
709        return formatElapsedTime(null, elapsedSeconds);
710    }
711
712    /**
713     * Formats an elapsed time in the form "MM:SS" or "H:MM:SS"
714     * for display on the call-in-progress screen.
715     *
716     * @param recycle {@link StringBuilder} to recycle, if possible
717     * @param elapsedSeconds the elapsed time in seconds.
718     */
719    public static String formatElapsedTime(StringBuilder recycle, long elapsedSeconds) {
720        initFormatStrings();
721
722        long hours = 0;
723        long minutes = 0;
724        long seconds = 0;
725
726        if (elapsedSeconds >= 3600) {
727            hours = elapsedSeconds / 3600;
728            elapsedSeconds -= hours * 3600;
729        }
730        if (elapsedSeconds >= 60) {
731            minutes = elapsedSeconds / 60;
732            elapsedSeconds -= minutes * 60;
733        }
734        seconds = elapsedSeconds;
735
736        String result;
737        if (hours > 0) {
738            return formatElapsedTime(recycle, sElapsedFormatHMMSS, hours, minutes, seconds);
739        } else {
740            return formatElapsedTime(recycle, sElapsedFormatMMSS, minutes, seconds);
741        }
742    }
743
744    /**
745     * Fast formatting of h:mm:ss
746     */
747    private static String formatElapsedTime(StringBuilder recycle, String format, long hours,
748            long minutes, long seconds) {
749        if (FAST_FORMAT_HMMSS.equals(format)) {
750            StringBuilder sb = recycle;
751            if (sb == null) {
752                sb = new StringBuilder(8);
753            } else {
754                sb.setLength(0);
755            }
756            sb.append(hours);
757            sb.append(TIME_SEPARATOR);
758            if (minutes < 10) {
759                sb.append(TIME_PADDING);
760            } else {
761                sb.append(toDigitChar(minutes / 10));
762            }
763            sb.append(toDigitChar(minutes % 10));
764            sb.append(TIME_SEPARATOR);
765            if (seconds < 10) {
766                sb.append(TIME_PADDING);
767            } else {
768                sb.append(toDigitChar(seconds / 10));
769            }
770            sb.append(toDigitChar(seconds % 10));
771            return sb.toString();
772        } else {
773            return String.format(format, hours, minutes, seconds);
774        }
775    }
776
777    /**
778     * Fast formatting of m:ss
779     */
780    private static String formatElapsedTime(StringBuilder recycle, String format, long minutes,
781            long seconds) {
782        if (FAST_FORMAT_MMSS.equals(format)) {
783            StringBuilder sb = recycle;
784            if (sb == null) {
785                sb = new StringBuilder(8);
786            } else {
787                sb.setLength(0);
788            }
789            if (minutes < 10) {
790                sb.append(TIME_PADDING);
791            } else {
792                sb.append(toDigitChar(minutes / 10));
793            }
794            sb.append(toDigitChar(minutes % 10));
795            sb.append(TIME_SEPARATOR);
796            if (seconds < 10) {
797                sb.append(TIME_PADDING);
798            } else {
799                sb.append(toDigitChar(seconds / 10));
800            }
801            sb.append(toDigitChar(seconds % 10));
802            return sb.toString();
803        } else {
804            return String.format(format, minutes, seconds);
805        }
806    }
807
808    private static char toDigitChar(long digit) {
809        return (char) (digit + '0');
810    }
811
812    /**
813     * Format a date / time such that if the then is on the same day as now, it shows
814     * just the time and if it's a different day, it shows just the date.
815     *
816     * <p>The parameters dateFormat and timeFormat should each be one of
817     * {@link java.text.DateFormat#DEFAULT},
818     * {@link java.text.DateFormat#FULL},
819     * {@link java.text.DateFormat#LONG},
820     * {@link java.text.DateFormat#MEDIUM}
821     * or
822     * {@link java.text.DateFormat#SHORT}
823     *
824     * @param then the date to format
825     * @param now the base time
826     * @param dateStyle how to format the date portion.
827     * @param timeStyle how to format the time portion.
828     */
829    public static final CharSequence formatSameDayTime(long then, long now,
830            int dateStyle, int timeStyle) {
831        Calendar thenCal = new GregorianCalendar();
832        thenCal.setTimeInMillis(then);
833        Date thenDate = thenCal.getTime();
834        Calendar nowCal = new GregorianCalendar();
835        nowCal.setTimeInMillis(now);
836
837        java.text.DateFormat f;
838
839        if (thenCal.get(Calendar.YEAR) == nowCal.get(Calendar.YEAR)
840                && thenCal.get(Calendar.MONTH) == nowCal.get(Calendar.MONTH)
841                && thenCal.get(Calendar.DAY_OF_MONTH) == nowCal.get(Calendar.DAY_OF_MONTH)) {
842            f = java.text.DateFormat.getTimeInstance(timeStyle);
843        } else {
844            f = java.text.DateFormat.getDateInstance(dateStyle);
845        }
846        return f.format(thenDate);
847    }
848
849    /**
850     * @hide
851     * @deprecated use {@link android.text.format.Time}
852     */
853    public static Calendar newCalendar(boolean zulu)
854    {
855        if (zulu)
856            return Calendar.getInstance(TimeZone.getTimeZone("GMT"));
857
858        return Calendar.getInstance();
859    }
860
861    /**
862     * @return true if the supplied when is today else false
863     */
864    public static boolean isToday(long when) {
865        Time time = new Time();
866        time.set(when);
867
868        int thenYear = time.year;
869        int thenMonth = time.month;
870        int thenMonthDay = time.monthDay;
871
872        time.set(System.currentTimeMillis());
873        return (thenYear == time.year)
874                && (thenMonth == time.month)
875                && (thenMonthDay == time.monthDay);
876    }
877
878    /**
879     * @hide
880     * @deprecated use {@link android.text.format.Time}
881     * Return true if this date string is local time
882     */
883    public static boolean isUTC(String s)
884    {
885        if (s.length() == 16 && s.charAt(15) == 'Z') {
886            return true;
887        }
888        if (s.length() == 9 && s.charAt(8) == 'Z') {
889            // XXX not sure if this case possible/valid
890            return true;
891        }
892        return false;
893    }
894
895    /**
896     * Return a string containing the date and time in RFC2445 format.
897     * Ensures that the time is written in UTC.  The Calendar class doesn't
898     * really help out with this, so this is slower than it ought to be.
899     *
900     * @param cal the date and time to write
901     * @hide
902     * @deprecated use {@link android.text.format.Time}
903     */
904    public static String writeDateTime(Calendar cal)
905    {
906        TimeZone tz = TimeZone.getTimeZone("GMT");
907        GregorianCalendar c = new GregorianCalendar(tz);
908        c.setTimeInMillis(cal.getTimeInMillis());
909        return writeDateTime(c, true);
910    }
911
912    /**
913     * Return a string containing the date and time in RFC2445 format.
914     *
915     * @param cal the date and time to write
916     * @param zulu If the calendar is in UTC, pass true, and a Z will
917     * be written at the end as per RFC2445.  Otherwise, the time is
918     * considered in localtime.
919     * @hide
920     * @deprecated use {@link android.text.format.Time}
921     */
922    public static String writeDateTime(Calendar cal, boolean zulu)
923    {
924        StringBuilder sb = new StringBuilder();
925        sb.ensureCapacity(16);
926        if (zulu) {
927            sb.setLength(16);
928            sb.setCharAt(15, 'Z');
929        } else {
930            sb.setLength(15);
931        }
932        return writeDateTime(cal, sb);
933    }
934
935    /**
936     * Return a string containing the date and time in RFC2445 format.
937     *
938     * @param cal the date and time to write
939     * @param sb a StringBuilder to use.  It is assumed that setLength
940     *           has already been called on sb to the appropriate length
941     *           which is sb.setLength(zulu ? 16 : 15)
942     * @hide
943     * @deprecated use {@link android.text.format.Time}
944     */
945    public static String writeDateTime(Calendar cal, StringBuilder sb)
946    {
947        int n;
948
949        n = cal.get(Calendar.YEAR);
950        sb.setCharAt(3, (char)('0'+n%10));
951        n /= 10;
952        sb.setCharAt(2, (char)('0'+n%10));
953        n /= 10;
954        sb.setCharAt(1, (char)('0'+n%10));
955        n /= 10;
956        sb.setCharAt(0, (char)('0'+n%10));
957
958        n = cal.get(Calendar.MONTH) + 1;
959        sb.setCharAt(5, (char)('0'+n%10));
960        n /= 10;
961        sb.setCharAt(4, (char)('0'+n%10));
962
963        n = cal.get(Calendar.DAY_OF_MONTH);
964        sb.setCharAt(7, (char)('0'+n%10));
965        n /= 10;
966        sb.setCharAt(6, (char)('0'+n%10));
967
968        sb.setCharAt(8, 'T');
969
970        n = cal.get(Calendar.HOUR_OF_DAY);
971        sb.setCharAt(10, (char)('0'+n%10));
972        n /= 10;
973        sb.setCharAt(9, (char)('0'+n%10));
974
975        n = cal.get(Calendar.MINUTE);
976        sb.setCharAt(12, (char)('0'+n%10));
977        n /= 10;
978        sb.setCharAt(11, (char)('0'+n%10));
979
980        n = cal.get(Calendar.SECOND);
981        sb.setCharAt(14, (char)('0'+n%10));
982        n /= 10;
983        sb.setCharAt(13, (char)('0'+n%10));
984
985        return sb.toString();
986    }
987
988    /**
989     * @hide
990     * @deprecated use {@link android.text.format.Time}
991     */
992    public static void assign(Calendar lval, Calendar rval)
993    {
994        // there should be a faster way.
995        lval.clear();
996        lval.setTimeInMillis(rval.getTimeInMillis());
997    }
998
999    /**
1000     * Formats a date or a time range according to the local conventions.
1001     * <p>
1002     * Note that this is a convenience method. Using it involves creating an
1003     * internal {@link java.util.Formatter} instance on-the-fly, which is
1004     * somewhat costly in terms of memory and time. This is probably acceptable
1005     * if you use the method only rarely, but if you rely on it for formatting a
1006     * large number of dates, consider creating and reusing your own
1007     * {@link java.util.Formatter} instance and use the version of
1008     * {@link #formatDateRange(Context, long, long, int) formatDateRange}
1009     * that takes a {@link java.util.Formatter}.
1010     *
1011     * @param context the context is required only if the time is shown
1012     * @param startMillis the start time in UTC milliseconds
1013     * @param endMillis the end time in UTC milliseconds
1014     * @param flags a bit mask of options See
1015     * {@link #formatDateRange(Context, Formatter, long, long, int, String) formatDateRange}
1016     * @return a string containing the formatted date/time range.
1017     */
1018    public static String formatDateRange(Context context, long startMillis,
1019            long endMillis, int flags) {
1020        Formatter f = new Formatter(new StringBuilder(50), Locale.getDefault());
1021        return formatDateRange(context, f, startMillis, endMillis, flags).toString();
1022    }
1023
1024    /**
1025     * Formats a date or a time range according to the local conventions.
1026     * <p>
1027     * Note that this is a convenience method for formatting the date or
1028     * time range in the local time zone. If you want to specify the time
1029     * zone please use
1030     * {@link #formatDateRange(Context, Formatter, long, long, int, String) formatDateRange}.
1031     *
1032     * @param context the context is required only if the time is shown
1033     * @param formatter the Formatter used for formatting the date range.
1034     * Note: be sure to call setLength(0) on StringBuilder passed to
1035     * the Formatter constructor unless you want the results to accumulate.
1036     * @param startMillis the start time in UTC milliseconds
1037     * @param endMillis the end time in UTC milliseconds
1038     * @param flags a bit mask of options See
1039     * {@link #formatDateRange(Context, Formatter, long, long, int, String) formatDateRange}
1040     * @return a string containing the formatted date/time range.
1041     */
1042    public static Formatter formatDateRange(Context context, Formatter formatter, long startMillis,
1043            long endMillis, int flags) {
1044        return formatDateRange(context, formatter, startMillis, endMillis, flags, null);
1045    }
1046
1047    /**
1048     * Formats a date or a time range according to the local conventions.
1049     *
1050     * <p>
1051     * Example output strings (date formats in these examples are shown using
1052     * the US date format convention but that may change depending on the
1053     * local settings):
1054     * <ul>
1055     *   <li>10:15am</li>
1056     *   <li>3:00pm - 4:00pm</li>
1057     *   <li>3pm - 4pm</li>
1058     *   <li>3PM - 4PM</li>
1059     *   <li>08:00 - 17:00</li>
1060     *   <li>Oct 9</li>
1061     *   <li>Tue, Oct 9</li>
1062     *   <li>October 9, 2007</li>
1063     *   <li>Oct 9 - 10</li>
1064     *   <li>Oct 9 - 10, 2007</li>
1065     *   <li>Oct 28 - Nov 3, 2007</li>
1066     *   <li>Dec 31, 2007 - Jan 1, 2008</li>
1067     *   <li>Oct 9, 8:00am - Oct 10, 5:00pm</li>
1068     *   <li>12/31/2007 - 01/01/2008</li>
1069     * </ul>
1070     *
1071     * <p>
1072     * The flags argument is a bitmask of options from the following list:
1073     *
1074     * <ul>
1075     *   <li>FORMAT_SHOW_TIME</li>
1076     *   <li>FORMAT_SHOW_WEEKDAY</li>
1077     *   <li>FORMAT_SHOW_YEAR</li>
1078     *   <li>FORMAT_NO_YEAR</li>
1079     *   <li>FORMAT_SHOW_DATE</li>
1080     *   <li>FORMAT_NO_MONTH_DAY</li>
1081     *   <li>FORMAT_12HOUR</li>
1082     *   <li>FORMAT_24HOUR</li>
1083     *   <li>FORMAT_CAP_AMPM</li>
1084     *   <li>FORMAT_NO_NOON</li>
1085     *   <li>FORMAT_CAP_NOON</li>
1086     *   <li>FORMAT_NO_MIDNIGHT</li>
1087     *   <li>FORMAT_CAP_MIDNIGHT</li>
1088     *   <li>FORMAT_UTC</li>
1089     *   <li>FORMAT_ABBREV_TIME</li>
1090     *   <li>FORMAT_ABBREV_WEEKDAY</li>
1091     *   <li>FORMAT_ABBREV_MONTH</li>
1092     *   <li>FORMAT_ABBREV_ALL</li>
1093     *   <li>FORMAT_NUMERIC_DATE</li>
1094     * </ul>
1095     *
1096     * <p>
1097     * If FORMAT_SHOW_TIME is set, the time is shown as part of the date range.
1098     * If the start and end time are the same, then just the start time is
1099     * shown.
1100     *
1101     * <p>
1102     * If FORMAT_SHOW_WEEKDAY is set, then the weekday is shown.
1103     *
1104     * <p>
1105     * If FORMAT_SHOW_YEAR is set, then the year is always shown.
1106     * If FORMAT_NO_YEAR is set, then the year is not shown.
1107     * If neither FORMAT_SHOW_YEAR nor FORMAT_NO_YEAR are set, then the year
1108     * is shown only if it is different from the current year, or if the start
1109     * and end dates fall on different years.  If both are set,
1110     * FORMAT_SHOW_YEAR takes precedence.
1111     *
1112     * <p>
1113     * Normally the date is shown unless the start and end day are the same.
1114     * If FORMAT_SHOW_DATE is set, then the date is always shown, even for
1115     * same day ranges.
1116     *
1117     * <p>
1118     * If FORMAT_NO_MONTH_DAY is set, then if the date is shown, just the
1119     * month name will be shown, not the day of the month.  For example,
1120     * "January, 2008" instead of "January 6 - 12, 2008".
1121     *
1122     * <p>
1123     * If FORMAT_CAP_AMPM is set and 12-hour time is used, then the "AM"
1124     * and "PM" are capitalized.  You should not use this flag
1125     * because in some locales these terms cannot be capitalized, and in
1126     * many others it doesn't make sense to do so even though it is possible.
1127     *
1128     * <p>
1129     * If FORMAT_NO_NOON is set and 12-hour time is used, then "12pm" is
1130     * shown instead of "noon".
1131     *
1132     * <p>
1133     * If FORMAT_CAP_NOON is set and 12-hour time is used, then "Noon" is
1134     * shown instead of "noon".  You should probably not use this flag
1135     * because in many locales it will not make sense to capitalize
1136     * the term.
1137     *
1138     * <p>
1139     * If FORMAT_NO_MIDNIGHT is set and 12-hour time is used, then "12am" is
1140     * shown instead of "midnight".
1141     *
1142     * <p>
1143     * If FORMAT_CAP_MIDNIGHT is set and 12-hour time is used, then "Midnight"
1144     * is shown instead of "midnight".  You should probably not use this
1145     * flag because in many locales it will not make sense to capitalize
1146     * the term.
1147     *
1148     * <p>
1149     * If FORMAT_12HOUR is set and the time is shown, then the time is
1150     * shown in the 12-hour time format. You should not normally set this.
1151     * Instead, let the time format be chosen automatically according to the
1152     * system settings. If both FORMAT_12HOUR and FORMAT_24HOUR are set, then
1153     * FORMAT_24HOUR takes precedence.
1154     *
1155     * <p>
1156     * If FORMAT_24HOUR is set and the time is shown, then the time is
1157     * shown in the 24-hour time format. You should not normally set this.
1158     * Instead, let the time format be chosen automatically according to the
1159     * system settings. If both FORMAT_12HOUR and FORMAT_24HOUR are set, then
1160     * FORMAT_24HOUR takes precedence.
1161     *
1162     * <p>
1163     * If FORMAT_UTC is set, then the UTC time zone is used for the start
1164     * and end milliseconds unless a time zone is specified. If a time zone
1165     * is specified it will be used regardless of the FORMAT_UTC flag.
1166     *
1167     * <p>
1168     * If FORMAT_ABBREV_TIME is set and 12-hour time format is used, then the
1169     * start and end times (if shown) are abbreviated by not showing the minutes
1170     * if they are zero.  For example, instead of "3:00pm" the time would be
1171     * abbreviated to "3pm".
1172     *
1173     * <p>
1174     * If FORMAT_ABBREV_WEEKDAY is set, then the weekday (if shown) is
1175     * abbreviated to a 3-letter string.
1176     *
1177     * <p>
1178     * If FORMAT_ABBREV_MONTH is set, then the month (if shown) is abbreviated
1179     * to a 3-letter string.
1180     *
1181     * <p>
1182     * If FORMAT_ABBREV_ALL is set, then the weekday and the month (if shown)
1183     * are abbreviated to 3-letter strings.
1184     *
1185     * <p>
1186     * If FORMAT_NUMERIC_DATE is set, then the date is shown in numeric format
1187     * instead of using the name of the month.  For example, "12/31/2008"
1188     * instead of "December 31, 2008".
1189     *
1190     * <p>
1191     * If the end date ends at 12:00am at the beginning of a day, it is
1192     * formatted as the end of the previous day in two scenarios:
1193     * <ul>
1194     *   <li>For single day events. This results in "8pm - midnight" instead of
1195     *       "Nov 10, 8pm - Nov 11, 12am".</li>
1196     *   <li>When the time is not displayed. This results in "Nov 10 - 11" for
1197     *       an event with a start date of Nov 10 and an end date of Nov 12 at
1198     *       00:00.</li>
1199     * </ul>
1200     *
1201     * @param context the context is required only if the time is shown
1202     * @param formatter the Formatter used for formatting the date range.
1203     * Note: be sure to call setLength(0) on StringBuilder passed to
1204     * the Formatter constructor unless you want the results to accumulate.
1205     * @param startMillis the start time in UTC milliseconds
1206     * @param endMillis the end time in UTC milliseconds
1207     * @param flags a bit mask of options
1208     * @param timeZone the time zone to compute the string in. Use null for local
1209     * or if the FORMAT_UTC flag is being used.
1210     *
1211     * @return the formatter with the formatted date/time range appended to the string buffer.
1212     */
1213    public static Formatter formatDateRange(Context context, Formatter formatter, long startMillis,
1214            long endMillis, int flags, String timeZone) {
1215        Resources res = Resources.getSystem();
1216        boolean showTime = (flags & FORMAT_SHOW_TIME) != 0;
1217        boolean showWeekDay = (flags & FORMAT_SHOW_WEEKDAY) != 0;
1218        boolean showYear = (flags & FORMAT_SHOW_YEAR) != 0;
1219        boolean noYear = (flags & FORMAT_NO_YEAR) != 0;
1220        boolean useUTC = (flags & FORMAT_UTC) != 0;
1221        boolean abbrevWeekDay = (flags & (FORMAT_ABBREV_WEEKDAY | FORMAT_ABBREV_ALL)) != 0;
1222        boolean abbrevMonth = (flags & (FORMAT_ABBREV_MONTH | FORMAT_ABBREV_ALL)) != 0;
1223        boolean noMonthDay = (flags & FORMAT_NO_MONTH_DAY) != 0;
1224        boolean numericDate = (flags & FORMAT_NUMERIC_DATE) != 0;
1225
1226        // If we're getting called with a single instant in time (from
1227        // e.g. formatDateTime(), below), then we can skip a lot of
1228        // computation below that'd otherwise be thrown out.
1229        boolean isInstant = (startMillis == endMillis);
1230
1231        Time startDate;
1232        if (timeZone != null) {
1233            startDate = new Time(timeZone);
1234        } else if (useUTC) {
1235            startDate = new Time(Time.TIMEZONE_UTC);
1236        } else {
1237            startDate = new Time();
1238        }
1239        startDate.set(startMillis);
1240
1241        Time endDate;
1242        int dayDistance;
1243        if (isInstant) {
1244            endDate = startDate;
1245            dayDistance = 0;
1246        } else {
1247            if (timeZone != null) {
1248                endDate = new Time(timeZone);
1249            } else if (useUTC) {
1250                endDate = new Time(Time.TIMEZONE_UTC);
1251            } else {
1252                endDate = new Time();
1253            }
1254            endDate.set(endMillis);
1255            int startJulianDay = Time.getJulianDay(startMillis, startDate.gmtoff);
1256            int endJulianDay = Time.getJulianDay(endMillis, endDate.gmtoff);
1257            dayDistance = endJulianDay - startJulianDay;
1258        }
1259
1260        if (!isInstant
1261            && (endDate.hour | endDate.minute | endDate.second) == 0
1262            && (!showTime || dayDistance <= 1)) {
1263            endDate.monthDay -= 1;
1264            endDate.normalize(true /* ignore isDst */);
1265        }
1266
1267        int startDay = startDate.monthDay;
1268        int startMonthNum = startDate.month;
1269        int startYear = startDate.year;
1270
1271        int endDay = endDate.monthDay;
1272        int endMonthNum = endDate.month;
1273        int endYear = endDate.year;
1274
1275        String startWeekDayString = "";
1276        String endWeekDayString = "";
1277        if (showWeekDay) {
1278            String weekDayFormat = "";
1279            if (abbrevWeekDay) {
1280                weekDayFormat = ABBREV_WEEKDAY_FORMAT;
1281            } else {
1282                weekDayFormat = WEEKDAY_FORMAT;
1283            }
1284            startWeekDayString = startDate.format(weekDayFormat);
1285            endWeekDayString = isInstant ? startWeekDayString : endDate.format(weekDayFormat);
1286        }
1287
1288        String startTimeString = "";
1289        String endTimeString = "";
1290        if (showTime) {
1291            String startTimeFormat = "";
1292            String endTimeFormat = "";
1293            boolean force24Hour = (flags & FORMAT_24HOUR) != 0;
1294            boolean force12Hour = (flags & FORMAT_12HOUR) != 0;
1295            boolean use24Hour;
1296            if (force24Hour) {
1297                use24Hour = true;
1298            } else if (force12Hour) {
1299                use24Hour = false;
1300            } else {
1301                use24Hour = DateFormat.is24HourFormat(context);
1302            }
1303            if (use24Hour) {
1304                startTimeFormat = endTimeFormat =
1305                    res.getString(com.android.internal.R.string.hour_minute_24);
1306            } else {
1307                boolean abbrevTime = (flags & (FORMAT_ABBREV_TIME | FORMAT_ABBREV_ALL)) != 0;
1308                boolean capAMPM = (flags & FORMAT_CAP_AMPM) != 0;
1309                boolean noNoon = (flags & FORMAT_NO_NOON) != 0;
1310                boolean capNoon = (flags & FORMAT_CAP_NOON) != 0;
1311                boolean noMidnight = (flags & FORMAT_NO_MIDNIGHT) != 0;
1312                boolean capMidnight = (flags & FORMAT_CAP_MIDNIGHT) != 0;
1313
1314                boolean startOnTheHour = startDate.minute == 0 && startDate.second == 0;
1315                boolean endOnTheHour = endDate.minute == 0 && endDate.second == 0;
1316                if (abbrevTime && startOnTheHour) {
1317                    if (capAMPM) {
1318                        startTimeFormat = res.getString(com.android.internal.R.string.hour_cap_ampm);
1319                    } else {
1320                        startTimeFormat = res.getString(com.android.internal.R.string.hour_ampm);
1321                    }
1322                } else {
1323                    if (capAMPM) {
1324                        startTimeFormat = res.getString(com.android.internal.R.string.hour_minute_cap_ampm);
1325                    } else {
1326                        startTimeFormat = res.getString(com.android.internal.R.string.hour_minute_ampm);
1327                    }
1328                }
1329
1330                // Don't waste time on setting endTimeFormat when
1331                // we're dealing with an instant, where we'll never
1332                // need the end point.  (It's the same as the start
1333                // point)
1334                if (!isInstant) {
1335                    if (abbrevTime && endOnTheHour) {
1336                        if (capAMPM) {
1337                            endTimeFormat = res.getString(com.android.internal.R.string.hour_cap_ampm);
1338                        } else {
1339                            endTimeFormat = res.getString(com.android.internal.R.string.hour_ampm);
1340                        }
1341                    } else {
1342                        if (capAMPM) {
1343                            endTimeFormat = res.getString(com.android.internal.R.string.hour_minute_cap_ampm);
1344                        } else {
1345                            endTimeFormat = res.getString(com.android.internal.R.string.hour_minute_ampm);
1346                        }
1347                    }
1348
1349                    if (endDate.hour == 12 && endOnTheHour && !noNoon) {
1350                        if (capNoon) {
1351                            endTimeFormat = res.getString(com.android.internal.R.string.Noon);
1352                        } else {
1353                            endTimeFormat = res.getString(com.android.internal.R.string.noon);
1354                        }
1355                    } else if (endDate.hour == 0 && endOnTheHour && !noMidnight) {
1356                        if (capMidnight) {
1357                            endTimeFormat = res.getString(com.android.internal.R.string.Midnight);
1358                        } else {
1359                            endTimeFormat = res.getString(com.android.internal.R.string.midnight);
1360                        }
1361                    }
1362                }
1363
1364                if (startDate.hour == 12 && startOnTheHour && !noNoon) {
1365                    if (capNoon) {
1366                        startTimeFormat = res.getString(com.android.internal.R.string.Noon);
1367                    } else {
1368                        startTimeFormat = res.getString(com.android.internal.R.string.noon);
1369                    }
1370                    // Don't show the start time starting at midnight.  Show
1371                    // 12am instead.
1372                }
1373            }
1374
1375            startTimeString = startDate.format(startTimeFormat);
1376            endTimeString = isInstant ? startTimeString : endDate.format(endTimeFormat);
1377        }
1378
1379        // Show the year if the user specified FORMAT_SHOW_YEAR or if
1380        // the starting and end years are different from each other
1381        // or from the current year.  But don't show the year if the
1382        // user specified FORMAT_NO_YEAR.
1383        if (showYear) {
1384            // No code... just a comment for clarity.  Keep showYear
1385            // on, as they enabled it with FORMAT_SHOW_YEAR.  This
1386            // takes precedence over them setting FORMAT_NO_YEAR.
1387        } else if (noYear) {
1388            // They explicitly didn't want a year.
1389            showYear = false;
1390        } else if (startYear != endYear) {
1391            showYear = true;
1392        } else {
1393            // Show the year if it's not equal to the current year.
1394            Time currentTime = new Time();
1395            currentTime.setToNow();
1396            showYear = startYear != currentTime.year;
1397        }
1398
1399        String defaultDateFormat, fullFormat, dateRange;
1400        if (numericDate) {
1401            defaultDateFormat = res.getString(com.android.internal.R.string.numeric_date);
1402        } else if (showYear) {
1403            if (abbrevMonth) {
1404                if (noMonthDay) {
1405                    defaultDateFormat = res.getString(com.android.internal.R.string.abbrev_month_year);
1406                } else {
1407                    defaultDateFormat = res.getString(com.android.internal.R.string.abbrev_month_day_year);
1408                }
1409            } else {
1410                if (noMonthDay) {
1411                    defaultDateFormat = res.getString(com.android.internal.R.string.month_year);
1412                } else {
1413                    defaultDateFormat = res.getString(com.android.internal.R.string.month_day_year);
1414                }
1415            }
1416        } else {
1417            if (abbrevMonth) {
1418                if (noMonthDay) {
1419                    defaultDateFormat = res.getString(com.android.internal.R.string.abbrev_month);
1420                } else {
1421                    defaultDateFormat = res.getString(com.android.internal.R.string.abbrev_month_day);
1422                }
1423            } else {
1424                if (noMonthDay) {
1425                    defaultDateFormat = res.getString(com.android.internal.R.string.month);
1426                } else {
1427                    defaultDateFormat = res.getString(com.android.internal.R.string.month_day);
1428                }
1429            }
1430        }
1431
1432        if (showWeekDay) {
1433            if (showTime) {
1434                fullFormat = res.getString(com.android.internal.R.string.wday1_date1_time1_wday2_date2_time2);
1435            } else {
1436                fullFormat = res.getString(com.android.internal.R.string.wday1_date1_wday2_date2);
1437            }
1438        } else {
1439            if (showTime) {
1440                fullFormat = res.getString(com.android.internal.R.string.date1_time1_date2_time2);
1441            } else {
1442                fullFormat = res.getString(com.android.internal.R.string.date1_date2);
1443            }
1444        }
1445
1446        if (noMonthDay && startMonthNum == endMonthNum && startYear == endYear) {
1447            // Example: "January, 2008"
1448            return formatter.format("%s", startDate.format(defaultDateFormat));
1449        }
1450
1451        if (startYear != endYear || noMonthDay) {
1452            // Different year or we are not showing the month day number.
1453            // Example: "December 31, 2007 - January 1, 2008"
1454            // Or: "January - February, 2008"
1455            String startDateString = startDate.format(defaultDateFormat);
1456            String endDateString = endDate.format(defaultDateFormat);
1457
1458            // The values that are used in a fullFormat string are specified
1459            // by position.
1460            return formatter.format(fullFormat,
1461                    startWeekDayString, startDateString, startTimeString,
1462                    endWeekDayString, endDateString, endTimeString);
1463        }
1464
1465        // Get the month, day, and year strings for the start and end dates
1466        String monthFormat;
1467        if (numericDate) {
1468            monthFormat = NUMERIC_MONTH_FORMAT;
1469        } else if (abbrevMonth) {
1470            monthFormat =
1471                res.getString(com.android.internal.R.string.short_format_month);
1472        } else {
1473            monthFormat = MONTH_FORMAT;
1474        }
1475        String startMonthString = startDate.format(monthFormat);
1476        String startMonthDayString = startDate.format(MONTH_DAY_FORMAT);
1477        String startYearString = startDate.format(YEAR_FORMAT);
1478
1479        String endMonthString = isInstant ? null : endDate.format(monthFormat);
1480        String endMonthDayString = isInstant ? null : endDate.format(MONTH_DAY_FORMAT);
1481        String endYearString = isInstant ? null : endDate.format(YEAR_FORMAT);
1482
1483        if (startMonthNum != endMonthNum) {
1484            // Same year, different month.
1485            // Example: "October 28 - November 3"
1486            // or: "Wed, Oct 31 - Sat, Nov 3, 2007"
1487            // or: "Oct 31, 8am - Sat, Nov 3, 2007, 5pm"
1488
1489            int index = 0;
1490            if (showWeekDay) index = 1;
1491            if (showYear) index += 2;
1492            if (showTime) index += 4;
1493            if (numericDate) index += 8;
1494            int resId = sameYearTable[index];
1495            fullFormat = res.getString(resId);
1496
1497            // The values that are used in a fullFormat string are specified
1498            // by position.
1499            return formatter.format(fullFormat,
1500                    startWeekDayString, startMonthString, startMonthDayString,
1501                    startYearString, startTimeString,
1502                    endWeekDayString, endMonthString, endMonthDayString,
1503                    endYearString, endTimeString);
1504        }
1505
1506        if (startDay != endDay) {
1507            // Same month, different day.
1508            int index = 0;
1509            if (showWeekDay) index = 1;
1510            if (showYear) index += 2;
1511            if (showTime) index += 4;
1512            if (numericDate) index += 8;
1513            int resId = sameMonthTable[index];
1514            fullFormat = res.getString(resId);
1515
1516            // The values that are used in a fullFormat string are specified
1517            // by position.
1518            return formatter.format(fullFormat,
1519                    startWeekDayString, startMonthString, startMonthDayString,
1520                    startYearString, startTimeString,
1521                    endWeekDayString, endMonthString, endMonthDayString,
1522                    endYearString, endTimeString);
1523        }
1524
1525        // Same start and end day
1526        boolean showDate = (flags & FORMAT_SHOW_DATE) != 0;
1527
1528        // If nothing was specified, then show the date.
1529        if (!showTime && !showDate && !showWeekDay) showDate = true;
1530
1531        // Compute the time string (example: "10:00 - 11:00 am")
1532        String timeString = "";
1533        if (showTime) {
1534            // If the start and end time are the same, then just show the
1535            // start time.
1536            if (isInstant) {
1537                // Same start and end time.
1538                // Example: "10:15 AM"
1539                timeString = startTimeString;
1540            } else {
1541                // Example: "10:00 - 11:00 am"
1542                String timeFormat = res.getString(com.android.internal.R.string.time1_time2);
1543                // Don't use the user supplied Formatter because the result will pollute the buffer.
1544                timeString = String.format(timeFormat, startTimeString, endTimeString);
1545            }
1546        }
1547
1548        // Figure out which full format to use.
1549        fullFormat = "";
1550        String dateString = "";
1551        if (showDate) {
1552            dateString = startDate.format(defaultDateFormat);
1553            if (showWeekDay) {
1554                if (showTime) {
1555                    // Example: "10:00 - 11:00 am, Tue, Oct 9"
1556                    fullFormat = res.getString(com.android.internal.R.string.time_wday_date);
1557                } else {
1558                    // Example: "Tue, Oct 9"
1559                    fullFormat = res.getString(com.android.internal.R.string.wday_date);
1560                }
1561            } else {
1562                if (showTime) {
1563                    // Example: "10:00 - 11:00 am, Oct 9"
1564                    fullFormat = res.getString(com.android.internal.R.string.time_date);
1565                } else {
1566                    // Example: "Oct 9"
1567                    return formatter.format("%s", dateString);
1568                }
1569            }
1570        } else if (showWeekDay) {
1571            if (showTime) {
1572                // Example: "10:00 - 11:00 am, Tue"
1573                fullFormat = res.getString(com.android.internal.R.string.time_wday);
1574            } else {
1575                // Example: "Tue"
1576                return formatter.format("%s", startWeekDayString);
1577            }
1578        } else if (showTime) {
1579            return formatter.format("%s", timeString);
1580        }
1581
1582        // The values that are used in a fullFormat string are specified
1583        // by position.
1584        return formatter.format(fullFormat, timeString, startWeekDayString, dateString);
1585    }
1586
1587    /**
1588     * Formats a date or a time according to the local conventions. There are
1589     * lots of options that allow the caller to control, for example, if the
1590     * time is shown, if the day of the week is shown, if the month name is
1591     * abbreviated, if noon is shown instead of 12pm, and so on. For the
1592     * complete list of options, see the documentation for
1593     * {@link #formatDateRange}.
1594     * <p>
1595     * Example output strings (date formats in these examples are shown using
1596     * the US date format convention but that may change depending on the
1597     * local settings):
1598     * <ul>
1599     *   <li>10:15am</li>
1600     *   <li>3:00pm</li>
1601     *   <li>3pm</li>
1602     *   <li>3PM</li>
1603     *   <li>08:00</li>
1604     *   <li>17:00</li>
1605     *   <li>noon</li>
1606     *   <li>Noon</li>
1607     *   <li>midnight</li>
1608     *   <li>Midnight</li>
1609     *   <li>Oct 31</li>
1610     *   <li>Oct 31, 2007</li>
1611     *   <li>October 31, 2007</li>
1612     *   <li>10am, Oct 31</li>
1613     *   <li>17:00, Oct 31</li>
1614     *   <li>Wed</li>
1615     *   <li>Wednesday</li>
1616     *   <li>10am, Wed, Oct 31</li>
1617     *   <li>Wed, Oct 31</li>
1618     *   <li>Wednesday, Oct 31</li>
1619     *   <li>Wed, Oct 31, 2007</li>
1620     *   <li>Wed, October 31</li>
1621     *   <li>10/31/2007</li>
1622     * </ul>
1623     *
1624     * @param context the context is required only if the time is shown
1625     * @param millis a point in time in UTC milliseconds
1626     * @param flags a bit mask of formatting options
1627     * @return a string containing the formatted date/time.
1628     */
1629    public static String formatDateTime(Context context, long millis, int flags) {
1630        return formatDateRange(context, millis, millis, flags);
1631    }
1632
1633    /**
1634     * @return a relative time string to display the time expressed by millis.  Times
1635     * are counted starting at midnight, which means that assuming that the current
1636     * time is March 31st, 0:30:
1637     * <ul>
1638     *   <li>"millis=0:10 today" will be displayed as "0:10"</li>
1639     *   <li>"millis=11:30pm the day before" will be displayed as "Mar 30"</li>
1640     * </ul>
1641     * If the given millis is in a different year, then the full date is
1642     * returned in numeric format (e.g., "10/12/2008").
1643     *
1644     * @param withPreposition If true, the string returned will include the correct
1645     * preposition ("at 9:20am", "on 10/12/2008" or "on May 29").
1646     */
1647    public static CharSequence getRelativeTimeSpanString(Context c, long millis,
1648            boolean withPreposition) {
1649
1650        String result;
1651        long now = System.currentTimeMillis();
1652        long span = Math.abs(now - millis);
1653
1654        synchronized (DateUtils.class) {
1655            if (sNowTime == null) {
1656                sNowTime = new Time();
1657            }
1658
1659            if (sThenTime == null) {
1660                sThenTime = new Time();
1661            }
1662
1663            sNowTime.set(now);
1664            sThenTime.set(millis);
1665
1666            int prepositionId;
1667            if (span < DAY_IN_MILLIS && sNowTime.weekDay == sThenTime.weekDay) {
1668                // Same day
1669                int flags = FORMAT_SHOW_TIME;
1670                result = formatDateRange(c, millis, millis, flags);
1671                prepositionId = R.string.preposition_for_time;
1672            } else if (sNowTime.year != sThenTime.year) {
1673                // Different years
1674                int flags = FORMAT_SHOW_DATE | FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE;
1675                result = formatDateRange(c, millis, millis, flags);
1676
1677                // This is a date (like "10/31/2008" so use the date preposition)
1678                prepositionId = R.string.preposition_for_date;
1679            } else {
1680                // Default
1681                int flags = FORMAT_SHOW_DATE | FORMAT_ABBREV_MONTH;
1682                result = formatDateRange(c, millis, millis, flags);
1683                prepositionId = R.string.preposition_for_date;
1684            }
1685            if (withPreposition) {
1686                Resources res = c.getResources();
1687                result = res.getString(prepositionId, result);
1688            }
1689        }
1690        return result;
1691    }
1692
1693    /**
1694     * Convenience function to return relative time string without preposition.
1695     * @param c context for resources
1696     * @param millis time in milliseconds
1697     * @return {@link CharSequence} containing relative time.
1698     * @see #getRelativeTimeSpanString(Context, long, boolean)
1699     */
1700    public static CharSequence getRelativeTimeSpanString(Context c, long millis) {
1701        return getRelativeTimeSpanString(c, millis, false /* no preposition */);
1702    }
1703
1704    private static Time sNowTime;
1705    private static Time sThenTime;
1706}
1707