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