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