DateUtils.java revision 6a84af0949b341593b4b25ed9bb365bd60ff1254
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 = getNumberOfDaysPassed(time, now);
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     * Returns the number of days passed between two dates.
526     *
527     * @param date1 first date
528     * @param date2 second date
529     * @return number of days passed between to dates.
530     */
531    private synchronized static long getNumberOfDaysPassed(long date1, long date2) {
532        if (sThenTime == null) {
533            sThenTime = new Time();
534        }
535        sThenTime.set(date1);
536        int day1 = Time.getJulianDay(date1, sThenTime.gmtoff);
537        sThenTime.set(date2);
538        int day2 = Time.getJulianDay(date2, sThenTime.gmtoff);
539        return Math.abs(day2 - day1);
540    }
541
542    /**
543     * Return string describing the elapsed time since startTime formatted like
544     * "[relative time/date], [time]".
545     * <p>
546     * Example output strings for the US date format.
547     * <ul>
548     * <li>3 mins ago, 10:15 AM</li>
549     * <li>yesterday, 12:20 PM</li>
550     * <li>Dec 12, 4:12 AM</li>
551     * <li>11/14/2007, 8:20 AM</li>
552     * </ul>
553     *
554     * @param time some time in the past.
555     * @param minResolution the minimum elapsed time (in milliseconds) to report
556     *            when showing relative times. For example, a time 3 seconds in
557     *            the past will be reported as "0 minutes ago" if this is set to
558     *            {@link #MINUTE_IN_MILLIS}.
559     * @param transitionResolution the elapsed time (in milliseconds) at which
560     *            to stop reporting relative measurements. Elapsed times greater
561     *            than this resolution will default to normal date formatting.
562     *            For example, will transition from "6 days ago" to "Dec 12"
563     *            when using {@link #WEEK_IN_MILLIS}.
564     */
565    public static CharSequence getRelativeDateTimeString(Context c, long time, long minResolution,
566            long transitionResolution, int flags) {
567        Resources r = Resources.getSystem();
568
569        long now = System.currentTimeMillis();
570        long duration = Math.abs(now - time);
571
572        // getRelativeTimeSpanString() doesn't correctly format relative dates
573        // above a week or exact dates below a day, so clamp
574        // transitionResolution as needed.
575        if (transitionResolution > WEEK_IN_MILLIS) {
576            transitionResolution = WEEK_IN_MILLIS;
577        } else if (transitionResolution < DAY_IN_MILLIS) {
578            transitionResolution = DAY_IN_MILLIS;
579        }
580
581        CharSequence timeClause = formatDateRange(c, time, time, FORMAT_SHOW_TIME);
582
583        String result;
584        if (duration < transitionResolution) {
585            CharSequence relativeClause = getRelativeTimeSpanString(time, now, minResolution, flags);
586            result = r.getString(com.android.internal.R.string.relative_time, relativeClause, timeClause);
587        } else {
588            CharSequence dateClause = getRelativeTimeSpanString(c, time, false);
589            result = r.getString(com.android.internal.R.string.date_time, dateClause, timeClause);
590        }
591
592        return result;
593    }
594
595    /**
596     * Returns a string describing a day relative to the current day. For example if the day is
597     * today this function returns "Today", if the day was a week ago it returns "7 days ago", and
598     * if the day is in 2 weeks it returns "in 14 days".
599     *
600     * @param r the resources to get the strings from
601     * @param day the relative day to describe in UTC milliseconds
602     * @param today the current time in UTC milliseconds
603     * @return a formatting string
604     */
605    private static final String getRelativeDayString(Resources r, long day, long today) {
606        Time startTime = new Time();
607        startTime.set(day);
608        Time currentTime = new Time();
609        currentTime.set(today);
610
611        int startDay = Time.getJulianDay(day, startTime.gmtoff);
612        int currentDay = Time.getJulianDay(today, currentTime.gmtoff);
613
614        int days = Math.abs(currentDay - startDay);
615        boolean past = (today > day);
616
617        if (days == 1) {
618            if (past) {
619                return r.getString(com.android.internal.R.string.yesterday);
620            } else {
621                return r.getString(com.android.internal.R.string.tomorrow);
622            }
623        } else if (days == 0) {
624            return r.getString(com.android.internal.R.string.today);
625        }
626
627        int resId;
628        if (past) {
629            resId = com.android.internal.R.plurals.num_days_ago;
630        } else {
631            resId = com.android.internal.R.plurals.in_num_days;
632        }
633
634        String format = r.getQuantityString(resId, days);
635        return String.format(format, days);
636    }
637
638    private static void initFormatStrings() {
639        synchronized (sLock) {
640            Resources r = Resources.getSystem();
641            Configuration cfg = r.getConfiguration();
642            if (sLastConfig == null || !sLastConfig.equals(cfg)) {
643                sLastConfig = cfg;
644                sStatusTimeFormat = java.text.DateFormat.getTimeInstance(java.text.DateFormat.SHORT);
645                sElapsedFormatMMSS = r.getString(com.android.internal.R.string.elapsed_time_short_format_mm_ss);
646                sElapsedFormatHMMSS = r.getString(com.android.internal.R.string.elapsed_time_short_format_h_mm_ss);
647            }
648        }
649    }
650
651    /**
652     * Format a time so it appears like it would in the status bar clock.
653     * @deprecated use {@link #DateFormat.getTimeFormat(Context)} instead.
654     * @hide
655     */
656    public static final CharSequence timeString(long millis) {
657        initFormatStrings();
658        return sStatusTimeFormat.format(millis);
659    }
660
661    /**
662     * Formats an elapsed time in the form "MM:SS" or "H:MM:SS"
663     * for display on the call-in-progress screen.
664     * @param elapsedSeconds the elapsed time in seconds.
665     */
666    public static String formatElapsedTime(long elapsedSeconds) {
667        return formatElapsedTime(null, elapsedSeconds);
668    }
669
670    /**
671     * Formats an elapsed time in the form "MM:SS" or "H:MM:SS"
672     * for display on the call-in-progress screen.
673     *
674     * @param recycle {@link StringBuilder} to recycle, if possible
675     * @param elapsedSeconds the elapsed time in seconds.
676     */
677    public static String formatElapsedTime(StringBuilder recycle, long elapsedSeconds) {
678        initFormatStrings();
679
680        long hours = 0;
681        long minutes = 0;
682        long seconds = 0;
683
684        if (elapsedSeconds >= 3600) {
685            hours = elapsedSeconds / 3600;
686            elapsedSeconds -= hours * 3600;
687        }
688        if (elapsedSeconds >= 60) {
689            minutes = elapsedSeconds / 60;
690            elapsedSeconds -= minutes * 60;
691        }
692        seconds = elapsedSeconds;
693
694        String result;
695        if (hours > 0) {
696            return formatElapsedTime(recycle, sElapsedFormatHMMSS, hours, minutes, seconds);
697        } else {
698            return formatElapsedTime(recycle, sElapsedFormatMMSS, minutes, seconds);
699        }
700    }
701
702    /**
703     * Fast formatting of h:mm:ss
704     */
705    private static String formatElapsedTime(StringBuilder recycle, String format, long hours,
706            long minutes, long seconds) {
707        if (FAST_FORMAT_HMMSS.equals(format)) {
708            StringBuilder sb = recycle;
709            if (sb == null) {
710                sb = new StringBuilder(8);
711            } else {
712                sb.setLength(0);
713            }
714            sb.append(hours);
715            sb.append(TIME_SEPARATOR);
716            if (minutes < 10) {
717                sb.append(TIME_PADDING);
718            } else {
719                sb.append(toDigitChar(minutes / 10));
720            }
721            sb.append(toDigitChar(minutes % 10));
722            sb.append(TIME_SEPARATOR);
723            if (seconds < 10) {
724                sb.append(TIME_PADDING);
725            } else {
726                sb.append(toDigitChar(seconds / 10));
727            }
728            sb.append(toDigitChar(seconds % 10));
729            return sb.toString();
730        } else {
731            return String.format(format, hours, minutes, seconds);
732        }
733    }
734
735    /**
736     * Fast formatting of m:ss
737     */
738    private static String formatElapsedTime(StringBuilder recycle, String format, long minutes,
739            long seconds) {
740        if (FAST_FORMAT_MMSS.equals(format)) {
741            StringBuilder sb = recycle;
742            if (sb == null) {
743                sb = new StringBuilder(8);
744            } else {
745                sb.setLength(0);
746            }
747            if (minutes < 10) {
748                sb.append(TIME_PADDING);
749            } else {
750                sb.append(toDigitChar(minutes / 10));
751            }
752            sb.append(toDigitChar(minutes % 10));
753            sb.append(TIME_SEPARATOR);
754            if (seconds < 10) {
755                sb.append(TIME_PADDING);
756            } else {
757                sb.append(toDigitChar(seconds / 10));
758            }
759            sb.append(toDigitChar(seconds % 10));
760            return sb.toString();
761        } else {
762            return String.format(format, minutes, seconds);
763        }
764    }
765
766    private static char toDigitChar(long digit) {
767        return (char) (digit + '0');
768    }
769
770    /**
771     * Format a date / time such that if the then is on the same day as now, it shows
772     * just the time and if it's a different day, it shows just the date.
773     *
774     * <p>The parameters dateFormat and timeFormat should each be one of
775     * {@link java.text.DateFormat#DEFAULT},
776     * {@link java.text.DateFormat#FULL},
777     * {@link java.text.DateFormat#LONG},
778     * {@link java.text.DateFormat#MEDIUM}
779     * or
780     * {@link java.text.DateFormat#SHORT}
781     *
782     * @param then the date to format
783     * @param now the base time
784     * @param dateStyle how to format the date portion.
785     * @param timeStyle how to format the time portion.
786     */
787    public static final CharSequence formatSameDayTime(long then, long now,
788            int dateStyle, int timeStyle) {
789        Calendar thenCal = new GregorianCalendar();
790        thenCal.setTimeInMillis(then);
791        Date thenDate = thenCal.getTime();
792        Calendar nowCal = new GregorianCalendar();
793        nowCal.setTimeInMillis(now);
794
795        java.text.DateFormat f;
796
797        if (thenCal.get(Calendar.YEAR) == nowCal.get(Calendar.YEAR)
798                && thenCal.get(Calendar.MONTH) == nowCal.get(Calendar.MONTH)
799                && thenCal.get(Calendar.DAY_OF_MONTH) == nowCal.get(Calendar.DAY_OF_MONTH)) {
800            f = java.text.DateFormat.getTimeInstance(timeStyle);
801        } else {
802            f = java.text.DateFormat.getDateInstance(dateStyle);
803        }
804        return f.format(thenDate);
805    }
806
807    /**
808     * @hide
809     * @deprecated use {@link android.text.format.Time}
810     */
811    public static Calendar newCalendar(boolean zulu)
812    {
813        if (zulu)
814            return Calendar.getInstance(TimeZone.getTimeZone("GMT"));
815
816        return Calendar.getInstance();
817    }
818
819    /**
820     * @return true if the supplied when is today else false
821     */
822    public static boolean isToday(long when) {
823        Time time = new Time();
824        time.set(when);
825
826        int thenYear = time.year;
827        int thenMonth = time.month;
828        int thenMonthDay = time.monthDay;
829
830        time.set(System.currentTimeMillis());
831        return (thenYear == time.year)
832                && (thenMonth == time.month)
833                && (thenMonthDay == time.monthDay);
834    }
835
836    /**
837     * @hide
838     * @deprecated use {@link android.text.format.Time}
839     */
840    private static final int ctoi(String str, int index)
841                                                throws DateException
842    {
843        char c = str.charAt(index);
844        if (c >= '0' && c <= '9') {
845            return (int)(c - '0');
846        }
847        throw new DateException("Expected numeric character.  Got '" +
848                                            c + "'");
849    }
850
851    /**
852     * @hide
853     * @deprecated use {@link android.text.format.Time}
854     */
855    private static final int check(int lowerBound, int upperBound, int value)
856                                                throws DateException
857    {
858        if (value >= lowerBound && value <= upperBound) {
859            return value;
860        }
861        throw new DateException("field out of bounds.  max=" + upperBound
862                                        + " value=" + value);
863    }
864
865    /**
866     * @hide
867     * @deprecated use {@link android.text.format.Time}
868     * Return true if this date string is local time
869     */
870    public static boolean isUTC(String s)
871    {
872        if (s.length() == 16 && s.charAt(15) == 'Z') {
873            return true;
874        }
875        if (s.length() == 9 && s.charAt(8) == 'Z') {
876            // XXX not sure if this case possible/valid
877            return true;
878        }
879        return false;
880    }
881
882
883    // note that month in Calendar is 0 based and in all other human
884    // representations, it's 1 based.
885    // Returns if the Z was present, meaning that the time is in UTC
886    /**
887     * @hide
888     * @deprecated use {@link android.text.format.Time}
889     */
890    public static boolean parseDateTime(String str, Calendar cal)
891                                                throws DateException
892    {
893        int len = str.length();
894        boolean dateTime = (len == 15 || len == 16) && str.charAt(8) == 'T';
895        boolean justDate = len == 8;
896        if (dateTime || justDate) {
897            cal.clear();
898            cal.set(Calendar.YEAR,
899                            ctoi(str, 0)*1000 + ctoi(str, 1)*100
900                            + ctoi(str, 2)*10 + ctoi(str, 3));
901            cal.set(Calendar.MONTH,
902                            check(0, 11, ctoi(str, 4)*10 + ctoi(str, 5) - 1));
903            cal.set(Calendar.DAY_OF_MONTH,
904                            check(1, 31, ctoi(str, 6)*10 + ctoi(str, 7)));
905            if (dateTime) {
906                cal.set(Calendar.HOUR_OF_DAY,
907                            check(0, 23, ctoi(str, 9)*10 + ctoi(str, 10)));
908                cal.set(Calendar.MINUTE,
909                            check(0, 59, ctoi(str, 11)*10 + ctoi(str, 12)));
910                cal.set(Calendar.SECOND,
911                            check(0, 59, ctoi(str, 13)*10 + ctoi(str, 14)));
912            }
913            if (justDate) {
914                cal.set(Calendar.HOUR_OF_DAY, 0);
915                cal.set(Calendar.MINUTE, 0);
916                cal.set(Calendar.SECOND, 0);
917                return true;
918            }
919            if (len == 15) {
920                return false;
921            }
922            if (str.charAt(15) == 'Z') {
923                return true;
924            }
925        }
926        throw new DateException("Invalid time (expected "
927                                + "YYYYMMDDThhmmssZ? got '" + str + "').");
928    }
929
930    /**
931     * Given a timezone string which can be null, and a dateTime string,
932     * set that time into a calendar.
933     * @hide
934     * @deprecated use {@link android.text.format.Time}
935     */
936    public static void parseDateTime(String tz, String dateTime, Calendar out)
937                                                throws DateException
938    {
939        TimeZone timezone;
940        if (DateUtils.isUTC(dateTime)) {
941            timezone = TimeZone.getTimeZone("UTC");
942        }
943        else if (tz == null) {
944            timezone = TimeZone.getDefault();
945        }
946        else {
947            timezone = TimeZone.getTimeZone(tz);
948        }
949
950        Calendar local = new GregorianCalendar(timezone);
951        DateUtils.parseDateTime(dateTime, local);
952
953        out.setTimeInMillis(local.getTimeInMillis());
954    }
955
956
957    /**
958     * Return a string containing the date and time in RFC2445 format.
959     * Ensures that the time is written in UTC.  The Calendar class doesn't
960     * really help out with this, so this is slower than it ought to be.
961     *
962     * @param cal the date and time to write
963     * @hide
964     * @deprecated use {@link android.text.format.Time}
965     */
966    public static String writeDateTime(Calendar cal)
967    {
968        TimeZone tz = TimeZone.getTimeZone("GMT");
969        GregorianCalendar c = new GregorianCalendar(tz);
970        c.setTimeInMillis(cal.getTimeInMillis());
971        return writeDateTime(c, true);
972    }
973
974    /**
975     * Return a string containing the date and time in RFC2445 format.
976     *
977     * @param cal the date and time to write
978     * @param zulu If the calendar is in UTC, pass true, and a Z will
979     * be written at the end as per RFC2445.  Otherwise, the time is
980     * considered in localtime.
981     * @hide
982     * @deprecated use {@link android.text.format.Time}
983     */
984    public static String writeDateTime(Calendar cal, boolean zulu)
985    {
986        StringBuilder sb = new StringBuilder();
987        sb.ensureCapacity(16);
988        if (zulu) {
989            sb.setLength(16);
990            sb.setCharAt(15, 'Z');
991        } else {
992            sb.setLength(15);
993        }
994        return writeDateTime(cal, sb);
995    }
996
997    /**
998     * Return a string containing the date and time in RFC2445 format.
999     *
1000     * @param cal the date and time to write
1001     * @param sb a StringBuilder to use.  It is assumed that setLength
1002     *           has already been called on sb to the appropriate length
1003     *           which is sb.setLength(zulu ? 16 : 15)
1004     * @hide
1005     * @deprecated use {@link android.text.format.Time}
1006     */
1007    public static String writeDateTime(Calendar cal, StringBuilder sb)
1008    {
1009        int n;
1010
1011        n = cal.get(Calendar.YEAR);
1012        sb.setCharAt(3, (char)('0'+n%10));
1013        n /= 10;
1014        sb.setCharAt(2, (char)('0'+n%10));
1015        n /= 10;
1016        sb.setCharAt(1, (char)('0'+n%10));
1017        n /= 10;
1018        sb.setCharAt(0, (char)('0'+n%10));
1019
1020        n = cal.get(Calendar.MONTH) + 1;
1021        sb.setCharAt(5, (char)('0'+n%10));
1022        n /= 10;
1023        sb.setCharAt(4, (char)('0'+n%10));
1024
1025        n = cal.get(Calendar.DAY_OF_MONTH);
1026        sb.setCharAt(7, (char)('0'+n%10));
1027        n /= 10;
1028        sb.setCharAt(6, (char)('0'+n%10));
1029
1030        sb.setCharAt(8, 'T');
1031
1032        n = cal.get(Calendar.HOUR_OF_DAY);
1033        sb.setCharAt(10, (char)('0'+n%10));
1034        n /= 10;
1035        sb.setCharAt(9, (char)('0'+n%10));
1036
1037        n = cal.get(Calendar.MINUTE);
1038        sb.setCharAt(12, (char)('0'+n%10));
1039        n /= 10;
1040        sb.setCharAt(11, (char)('0'+n%10));
1041
1042        n = cal.get(Calendar.SECOND);
1043        sb.setCharAt(14, (char)('0'+n%10));
1044        n /= 10;
1045        sb.setCharAt(13, (char)('0'+n%10));
1046
1047        return sb.toString();
1048    }
1049
1050    /**
1051     * @hide
1052     * @deprecated use {@link android.text.format.Time}
1053     */
1054    public static void assign(Calendar lval, Calendar rval)
1055    {
1056        // there should be a faster way.
1057        lval.clear();
1058        lval.setTimeInMillis(rval.getTimeInMillis());
1059    }
1060
1061    /**
1062     * Formats a date or a time range according to the local conventions.
1063     * <p>
1064     * Note that this is a convenience method. Using it involves creating an
1065     * internal {@link java.util.Formatter} instance on-the-fly, which is
1066     * somewhat costly in terms of memory and time. This is probably acceptable
1067     * if you use the method only rarely, but if you rely on it for formatting a
1068     * large number of dates, consider creating and reusing your own
1069     * {@link java.util.Formatter} instance and use the version of
1070     * {@link #formatDateRange(Context, long, long, int) formatDateRange}
1071     * that takes a {@link java.util.Formatter}.
1072     *
1073     * @param context the context is required only if the time is shown
1074     * @param startMillis the start time in UTC milliseconds
1075     * @param endMillis the end time in UTC milliseconds
1076     * @param flags a bit mask of options See
1077     * {@link #formatDateRange(Context, long, long, int) formatDateRange}
1078     * @return a string containing the formatted date/time range.
1079     */
1080    public static String formatDateRange(Context context, long startMillis,
1081            long endMillis, int flags) {
1082        Formatter f = new Formatter(new StringBuilder(50), Locale.getDefault());
1083        return formatDateRange(context, f, startMillis, endMillis, flags).toString();
1084    }
1085
1086    /**
1087     * Formats a date or a time range according to the local conventions.
1088     *
1089     * <p>
1090     * Example output strings (date formats in these examples are shown using
1091     * the US date format convention but that may change depending on the
1092     * local settings):
1093     * <ul>
1094     *   <li>10:15am</li>
1095     *   <li>3:00pm - 4:00pm</li>
1096     *   <li>3pm - 4pm</li>
1097     *   <li>3PM - 4PM</li>
1098     *   <li>08:00 - 17:00</li>
1099     *   <li>Oct 9</li>
1100     *   <li>Tue, Oct 9</li>
1101     *   <li>October 9, 2007</li>
1102     *   <li>Oct 9 - 10</li>
1103     *   <li>Oct 9 - 10, 2007</li>
1104     *   <li>Oct 28 - Nov 3, 2007</li>
1105     *   <li>Dec 31, 2007 - Jan 1, 2008</li>
1106     *   <li>Oct 9, 8:00am - Oct 10, 5:00pm</li>
1107     *   <li>12/31/2007 - 01/01/2008</li>
1108     * </ul>
1109     *
1110     * <p>
1111     * The flags argument is a bitmask of options from the following list:
1112     *
1113     * <ul>
1114     *   <li>FORMAT_SHOW_TIME</li>
1115     *   <li>FORMAT_SHOW_WEEKDAY</li>
1116     *   <li>FORMAT_SHOW_YEAR</li>
1117     *   <li>FORMAT_NO_YEAR</li>
1118     *   <li>FORMAT_SHOW_DATE</li>
1119     *   <li>FORMAT_NO_MONTH_DAY</li>
1120     *   <li>FORMAT_12HOUR</li>
1121     *   <li>FORMAT_24HOUR</li>
1122     *   <li>FORMAT_CAP_AMPM</li>
1123     *   <li>FORMAT_NO_NOON</li>
1124     *   <li>FORMAT_CAP_NOON</li>
1125     *   <li>FORMAT_NO_MIDNIGHT</li>
1126     *   <li>FORMAT_CAP_MIDNIGHT</li>
1127     *   <li>FORMAT_UTC</li>
1128     *   <li>FORMAT_ABBREV_TIME</li>
1129     *   <li>FORMAT_ABBREV_WEEKDAY</li>
1130     *   <li>FORMAT_ABBREV_MONTH</li>
1131     *   <li>FORMAT_ABBREV_ALL</li>
1132     *   <li>FORMAT_NUMERIC_DATE</li>
1133     * </ul>
1134     *
1135     * <p>
1136     * If FORMAT_SHOW_TIME is set, the time is shown as part of the date range.
1137     * If the start and end time are the same, then just the start time is
1138     * shown.
1139     *
1140     * <p>
1141     * If FORMAT_SHOW_WEEKDAY is set, then the weekday is shown.
1142     *
1143     * <p>
1144     * If FORMAT_SHOW_YEAR is set, then the year is always shown.
1145     * If FORMAT_NO_YEAR is set, then the year is not shown.
1146     * If neither FORMAT_SHOW_YEAR nor FORMAT_NO_YEAR are set, then the year
1147     * is shown only if it is different from the current year, or if the start
1148     * and end dates fall on different years.  If both are set,
1149     * FORMAT_SHOW_YEAR takes precedence.
1150     *
1151     * <p>
1152     * Normally the date is shown unless the start and end day are the same.
1153     * If FORMAT_SHOW_DATE is set, then the date is always shown, even for
1154     * same day ranges.
1155     *
1156     * <p>
1157     * If FORMAT_NO_MONTH_DAY is set, then if the date is shown, just the
1158     * month name will be shown, not the day of the month.  For example,
1159     * "January, 2008" instead of "January 6 - 12, 2008".
1160     *
1161     * <p>
1162     * If FORMAT_CAP_AMPM is set and 12-hour time is used, then the "AM"
1163     * and "PM" are capitalized.  You should not use this flag
1164     * because in some locales these terms cannot be capitalized, and in
1165     * many others it doesn't make sense to do so even though it is possible.
1166     *
1167     * <p>
1168     * If FORMAT_NO_NOON is set and 12-hour time is used, then "12pm" is
1169     * shown instead of "noon".
1170     *
1171     * <p>
1172     * If FORMAT_CAP_NOON is set and 12-hour time is used, then "Noon" is
1173     * shown instead of "noon".  You should probably not use this flag
1174     * because in many locales it will not make sense to capitalize
1175     * the term.
1176     *
1177     * <p>
1178     * If FORMAT_NO_MIDNIGHT is set and 12-hour time is used, then "12am" is
1179     * shown instead of "midnight".
1180     *
1181     * <p>
1182     * If FORMAT_CAP_MIDNIGHT is set and 12-hour time is used, then "Midnight"
1183     * is shown instead of "midnight".  You should probably not use this
1184     * flag because in many locales it will not make sense to capitalize
1185     * the term.
1186     *
1187     * <p>
1188     * If FORMAT_12HOUR is set and the time is shown, then the time is
1189     * shown in the 12-hour time format. You should not normally set this.
1190     * Instead, let the time format be chosen automatically according to the
1191     * system settings. If both FORMAT_12HOUR and FORMAT_24HOUR are set, then
1192     * FORMAT_24HOUR takes precedence.
1193     *
1194     * <p>
1195     * If FORMAT_24HOUR is set and the time is shown, then the time is
1196     * shown in the 24-hour time format. You should not normally set this.
1197     * Instead, let the time format be chosen automatically according to the
1198     * system settings. If both FORMAT_12HOUR and FORMAT_24HOUR are set, then
1199     * FORMAT_24HOUR takes precedence.
1200     *
1201     * <p>
1202     * If FORMAT_UTC is set, then the UTC timezone is used for the start
1203     * and end milliseconds.
1204     *
1205     * <p>
1206     * If FORMAT_ABBREV_TIME is set and 12-hour time format is used, then the
1207     * start and end times (if shown) are abbreviated by not showing the minutes
1208     * if they are zero.  For example, instead of "3:00pm" the time would be
1209     * abbreviated to "3pm".
1210     *
1211     * <p>
1212     * If FORMAT_ABBREV_WEEKDAY is set, then the weekday (if shown) is
1213     * abbreviated to a 3-letter string.
1214     *
1215     * <p>
1216     * If FORMAT_ABBREV_MONTH is set, then the month (if shown) is abbreviated
1217     * to a 3-letter string.
1218     *
1219     * <p>
1220     * If FORMAT_ABBREV_ALL is set, then the weekday and the month (if shown)
1221     * are abbreviated to 3-letter strings.
1222     *
1223     * <p>
1224     * If FORMAT_NUMERIC_DATE is set, then the date is shown in numeric format
1225     * instead of using the name of the month.  For example, "12/31/2008"
1226     * instead of "December 31, 2008".
1227     *
1228     * @param context the context is required only if the time is shown
1229     * @param formatter the Formatter used for formatting the date range.
1230     * Note: be sure to call setLength(0) on StringBuilder passed to
1231     * the Formatter constructor unless you want the results to accumulate.
1232     * @param startMillis the start time in UTC milliseconds
1233     * @param endMillis the end time in UTC milliseconds
1234     * @param flags a bit mask of options
1235     *
1236     * @return the formatter with the formatted date/time range appended to the string buffer.
1237     */
1238    public static Formatter formatDateRange(Context context, Formatter formatter, long startMillis,
1239            long endMillis, int flags) {
1240        Resources res = Resources.getSystem();
1241        boolean showTime = (flags & FORMAT_SHOW_TIME) != 0;
1242        boolean showWeekDay = (flags & FORMAT_SHOW_WEEKDAY) != 0;
1243        boolean showYear = (flags & FORMAT_SHOW_YEAR) != 0;
1244        boolean noYear = (flags & FORMAT_NO_YEAR) != 0;
1245        boolean useUTC = (flags & FORMAT_UTC) != 0;
1246        boolean abbrevWeekDay = (flags & (FORMAT_ABBREV_WEEKDAY | FORMAT_ABBREV_ALL)) != 0;
1247        boolean abbrevMonth = (flags & (FORMAT_ABBREV_MONTH | FORMAT_ABBREV_ALL)) != 0;
1248        boolean noMonthDay = (flags & FORMAT_NO_MONTH_DAY) != 0;
1249        boolean numericDate = (flags & FORMAT_NUMERIC_DATE) != 0;
1250
1251        // If we're getting called with a single instant in time (from
1252        // e.g. formatDateTime(), below), then we can skip a lot of
1253        // computation below that'd otherwise be thrown out.
1254        boolean isInstant = (startMillis == endMillis);
1255
1256        Time startDate = useUTC ? new Time(Time.TIMEZONE_UTC) : new Time();
1257        startDate.set(startMillis);
1258
1259        Time endDate;
1260        int dayDistance;
1261        if (isInstant) {
1262            endDate = startDate;
1263            dayDistance = 0;
1264        } else {
1265            endDate = useUTC ? new Time(Time.TIMEZONE_UTC) : new Time();
1266            endDate.set(endMillis);
1267            int startJulianDay = Time.getJulianDay(startMillis, startDate.gmtoff);
1268            int endJulianDay = Time.getJulianDay(endMillis, endDate.gmtoff);
1269            dayDistance = endJulianDay - startJulianDay;
1270        }
1271
1272        // If the end date ends at 12am at the beginning of a day,
1273        // then modify it to make it look like it ends at midnight on
1274        // the previous day.  This will allow us to display "8pm - midnight",
1275        // for example, instead of "Nov 10, 8pm - Nov 11, 12am". But we only do
1276        // this if it is midnight of the same day as the start date because
1277        // for multiple-day events, an end time of "midnight on Nov 11" is
1278        // ambiguous and confusing (is that midnight the start of Nov 11, or
1279        // the end of Nov 11?).
1280        // If we are not showing the time then also adjust the end date
1281        // for multiple-day events.  This is to allow us to display, for
1282        // example, "Nov 10 -11" for an event with a start date of Nov 10
1283        // and an end date of Nov 12 at 00:00.
1284        // If the start and end time are the same, then skip this and don't
1285        // adjust the date.
1286        if (!isInstant
1287            && (endDate.hour | endDate.minute | endDate.second) == 0
1288            && (!showTime || dayDistance <= 1)) {
1289            endDate.monthDay -= 1;
1290            endDate.normalize(true /* ignore isDst */);
1291        }
1292
1293        int startDay = startDate.monthDay;
1294        int startMonthNum = startDate.month;
1295        int startYear = startDate.year;
1296
1297        int endDay = endDate.monthDay;
1298        int endMonthNum = endDate.month;
1299        int endYear = endDate.year;
1300
1301        String startWeekDayString = "";
1302        String endWeekDayString = "";
1303        if (showWeekDay) {
1304            String weekDayFormat = "";
1305            if (abbrevWeekDay) {
1306                weekDayFormat = ABBREV_WEEKDAY_FORMAT;
1307            } else {
1308                weekDayFormat = WEEKDAY_FORMAT;
1309            }
1310            startWeekDayString = startDate.format(weekDayFormat);
1311            endWeekDayString = isInstant ? startWeekDayString : endDate.format(weekDayFormat);
1312        }
1313
1314        String startTimeString = "";
1315        String endTimeString = "";
1316        if (showTime) {
1317            String startTimeFormat = "";
1318            String endTimeFormat = "";
1319            boolean force24Hour = (flags & FORMAT_24HOUR) != 0;
1320            boolean force12Hour = (flags & FORMAT_12HOUR) != 0;
1321            boolean use24Hour;
1322            if (force24Hour) {
1323                use24Hour = true;
1324            } else if (force12Hour) {
1325                use24Hour = false;
1326            } else {
1327                use24Hour = DateFormat.is24HourFormat(context);
1328            }
1329            if (use24Hour) {
1330                startTimeFormat = endTimeFormat =
1331                    res.getString(com.android.internal.R.string.hour_minute_24);
1332            } else {
1333                boolean abbrevTime = (flags & (FORMAT_ABBREV_TIME | FORMAT_ABBREV_ALL)) != 0;
1334                boolean capAMPM = (flags & FORMAT_CAP_AMPM) != 0;
1335                boolean noNoon = (flags & FORMAT_NO_NOON) != 0;
1336                boolean capNoon = (flags & FORMAT_CAP_NOON) != 0;
1337                boolean noMidnight = (flags & FORMAT_NO_MIDNIGHT) != 0;
1338                boolean capMidnight = (flags & FORMAT_CAP_MIDNIGHT) != 0;
1339
1340                boolean startOnTheHour = startDate.minute == 0 && startDate.second == 0;
1341                boolean endOnTheHour = endDate.minute == 0 && endDate.second == 0;
1342                if (abbrevTime && startOnTheHour) {
1343                    if (capAMPM) {
1344                        startTimeFormat = res.getString(com.android.internal.R.string.hour_cap_ampm);
1345                    } else {
1346                        startTimeFormat = res.getString(com.android.internal.R.string.hour_ampm);
1347                    }
1348                } else {
1349                    if (capAMPM) {
1350                        startTimeFormat = res.getString(com.android.internal.R.string.hour_minute_cap_ampm);
1351                    } else {
1352                        startTimeFormat = res.getString(com.android.internal.R.string.hour_minute_ampm);
1353                    }
1354                }
1355
1356                // Don't waste time on setting endTimeFormat when
1357                // we're dealing with an instant, where we'll never
1358                // need the end point.  (It's the same as the start
1359                // point)
1360                if (!isInstant) {
1361                    if (abbrevTime && endOnTheHour) {
1362                        if (capAMPM) {
1363                            endTimeFormat = res.getString(com.android.internal.R.string.hour_cap_ampm);
1364                        } else {
1365                            endTimeFormat = res.getString(com.android.internal.R.string.hour_ampm);
1366                        }
1367                    } else {
1368                        if (capAMPM) {
1369                            endTimeFormat = res.getString(com.android.internal.R.string.hour_minute_cap_ampm);
1370                        } else {
1371                            endTimeFormat = res.getString(com.android.internal.R.string.hour_minute_ampm);
1372                        }
1373                    }
1374
1375                    if (endDate.hour == 12 && endOnTheHour && !noNoon) {
1376                        if (capNoon) {
1377                            endTimeFormat = res.getString(com.android.internal.R.string.Noon);
1378                        } else {
1379                            endTimeFormat = res.getString(com.android.internal.R.string.noon);
1380                        }
1381                    } else if (endDate.hour == 0 && endOnTheHour && !noMidnight) {
1382                        if (capMidnight) {
1383                            endTimeFormat = res.getString(com.android.internal.R.string.Midnight);
1384                        } else {
1385                            endTimeFormat = res.getString(com.android.internal.R.string.midnight);
1386                        }
1387                    }
1388                }
1389
1390                if (startDate.hour == 12 && startOnTheHour && !noNoon) {
1391                    if (capNoon) {
1392                        startTimeFormat = res.getString(com.android.internal.R.string.Noon);
1393                    } else {
1394                        startTimeFormat = res.getString(com.android.internal.R.string.noon);
1395                    }
1396                    // Don't show the start time starting at midnight.  Show
1397                    // 12am instead.
1398                }
1399            }
1400
1401            startTimeString = startDate.format(startTimeFormat);
1402            endTimeString = isInstant ? startTimeString : endDate.format(endTimeFormat);
1403        }
1404
1405        // Show the year if the user specified FORMAT_SHOW_YEAR or if
1406        // the starting and end years are different from each other
1407        // or from the current year.  But don't show the year if the
1408        // user specified FORMAT_NO_YEAR.
1409        if (showYear) {
1410            // No code... just a comment for clarity.  Keep showYear
1411            // on, as they enabled it with FORMAT_SHOW_YEAR.  This
1412            // takes precedence over them setting FORMAT_NO_YEAR.
1413        } else if (noYear) {
1414            // They explicitly didn't want a year.
1415            showYear = false;
1416        } else if (startYear != endYear) {
1417            showYear = true;
1418        } else {
1419            // Show the year if it's not equal to the current year.
1420            Time currentTime = new Time();
1421            currentTime.setToNow();
1422            showYear = startYear != currentTime.year;
1423        }
1424
1425        String defaultDateFormat, fullFormat, dateRange;
1426        if (numericDate) {
1427            defaultDateFormat = res.getString(com.android.internal.R.string.numeric_date);
1428        } else if (showYear) {
1429            if (abbrevMonth) {
1430                if (noMonthDay) {
1431                    defaultDateFormat = res.getString(com.android.internal.R.string.abbrev_month_year);
1432                } else {
1433                    defaultDateFormat = res.getString(com.android.internal.R.string.abbrev_month_day_year);
1434                }
1435            } else {
1436                if (noMonthDay) {
1437                    defaultDateFormat = res.getString(com.android.internal.R.string.month_year);
1438                } else {
1439                    defaultDateFormat = res.getString(com.android.internal.R.string.month_day_year);
1440                }
1441            }
1442        } else {
1443            if (abbrevMonth) {
1444                if (noMonthDay) {
1445                    defaultDateFormat = res.getString(com.android.internal.R.string.abbrev_month);
1446                } else {
1447                    defaultDateFormat = res.getString(com.android.internal.R.string.abbrev_month_day);
1448                }
1449            } else {
1450                if (noMonthDay) {
1451                    defaultDateFormat = res.getString(com.android.internal.R.string.month);
1452                } else {
1453                    defaultDateFormat = res.getString(com.android.internal.R.string.month_day);
1454                }
1455            }
1456        }
1457
1458        if (showWeekDay) {
1459            if (showTime) {
1460                fullFormat = res.getString(com.android.internal.R.string.wday1_date1_time1_wday2_date2_time2);
1461            } else {
1462                fullFormat = res.getString(com.android.internal.R.string.wday1_date1_wday2_date2);
1463            }
1464        } else {
1465            if (showTime) {
1466                fullFormat = res.getString(com.android.internal.R.string.date1_time1_date2_time2);
1467            } else {
1468                fullFormat = res.getString(com.android.internal.R.string.date1_date2);
1469            }
1470        }
1471
1472        if (noMonthDay && startMonthNum == endMonthNum) {
1473            // Example: "January, 2008"
1474            return formatter.format("%s", startDate.format(defaultDateFormat));
1475        }
1476
1477        if (startYear != endYear || noMonthDay) {
1478            // Different year or we are not showing the month day number.
1479            // Example: "December 31, 2007 - January 1, 2008"
1480            // Or: "January - February, 2008"
1481            String startDateString = startDate.format(defaultDateFormat);
1482            String endDateString = endDate.format(defaultDateFormat);
1483
1484            // The values that are used in a fullFormat string are specified
1485            // by position.
1486            return formatter.format(fullFormat,
1487                    startWeekDayString, startDateString, startTimeString,
1488                    endWeekDayString, endDateString, endTimeString);
1489        }
1490
1491        // Get the month, day, and year strings for the start and end dates
1492        String monthFormat;
1493        if (numericDate) {
1494            monthFormat = NUMERIC_MONTH_FORMAT;
1495        } else if (abbrevMonth) {
1496            monthFormat =
1497                res.getString(com.android.internal.R.string.short_format_month);
1498        } else {
1499            monthFormat = MONTH_FORMAT;
1500        }
1501        String startMonthString = startDate.format(monthFormat);
1502        String startMonthDayString = startDate.format(MONTH_DAY_FORMAT);
1503        String startYearString = startDate.format(YEAR_FORMAT);
1504
1505        String endMonthString = isInstant ? null : endDate.format(monthFormat);
1506        String endMonthDayString = isInstant ? null : endDate.format(MONTH_DAY_FORMAT);
1507        String endYearString = isInstant ? null : endDate.format(YEAR_FORMAT);
1508
1509        if (startMonthNum != endMonthNum) {
1510            // Same year, different month.
1511            // Example: "October 28 - November 3"
1512            // or: "Wed, Oct 31 - Sat, Nov 3, 2007"
1513            // or: "Oct 31, 8am - Sat, Nov 3, 2007, 5pm"
1514
1515            int index = 0;
1516            if (showWeekDay) index = 1;
1517            if (showYear) index += 2;
1518            if (showTime) index += 4;
1519            if (numericDate) index += 8;
1520            int resId = sameYearTable[index];
1521            fullFormat = res.getString(resId);
1522
1523            // The values that are used in a fullFormat string are specified
1524            // by position.
1525            return formatter.format(fullFormat,
1526                    startWeekDayString, startMonthString, startMonthDayString,
1527                    startYearString, startTimeString,
1528                    endWeekDayString, endMonthString, endMonthDayString,
1529                    endYearString, endTimeString);
1530        }
1531
1532        if (startDay != endDay) {
1533            // Same month, different day.
1534            int index = 0;
1535            if (showWeekDay) index = 1;
1536            if (showYear) index += 2;
1537            if (showTime) index += 4;
1538            if (numericDate) index += 8;
1539            int resId = sameMonthTable[index];
1540            fullFormat = res.getString(resId);
1541
1542            // The values that are used in a fullFormat string are specified
1543            // by position.
1544            return formatter.format(fullFormat,
1545                    startWeekDayString, startMonthString, startMonthDayString,
1546                    startYearString, startTimeString,
1547                    endWeekDayString, endMonthString, endMonthDayString,
1548                    endYearString, endTimeString);
1549        }
1550
1551        // Same start and end day
1552        boolean showDate = (flags & FORMAT_SHOW_DATE) != 0;
1553
1554        // If nothing was specified, then show the date.
1555        if (!showTime && !showDate && !showWeekDay) showDate = true;
1556
1557        // Compute the time string (example: "10:00 - 11:00 am")
1558        String timeString = "";
1559        if (showTime) {
1560            // If the start and end time are the same, then just show the
1561            // start time.
1562            if (isInstant) {
1563                // Same start and end time.
1564                // Example: "10:15 AM"
1565                timeString = startTimeString;
1566            } else {
1567                // Example: "10:00 - 11:00 am"
1568                String timeFormat = res.getString(com.android.internal.R.string.time1_time2);
1569                // Don't use the user supplied Formatter because the result will pollute the buffer.
1570                timeString = String.format(timeFormat, startTimeString, endTimeString);
1571            }
1572        }
1573
1574        // Figure out which full format to use.
1575        fullFormat = "";
1576        String dateString = "";
1577        if (showDate) {
1578            dateString = startDate.format(defaultDateFormat);
1579            if (showWeekDay) {
1580                if (showTime) {
1581                    // Example: "10:00 - 11:00 am, Tue, Oct 9"
1582                    fullFormat = res.getString(com.android.internal.R.string.time_wday_date);
1583                } else {
1584                    // Example: "Tue, Oct 9"
1585                    fullFormat = res.getString(com.android.internal.R.string.wday_date);
1586                }
1587            } else {
1588                if (showTime) {
1589                    // Example: "10:00 - 11:00 am, Oct 9"
1590                    fullFormat = res.getString(com.android.internal.R.string.time_date);
1591                } else {
1592                    // Example: "Oct 9"
1593                    return formatter.format("%s", dateString);
1594                }
1595            }
1596        } else if (showWeekDay) {
1597            if (showTime) {
1598                // Example: "10:00 - 11:00 am, Tue"
1599                fullFormat = res.getString(com.android.internal.R.string.time_wday);
1600            } else {
1601                // Example: "Tue"
1602                return formatter.format("%s", startWeekDayString);
1603            }
1604        } else if (showTime) {
1605            return formatter.format("%s", timeString);
1606        }
1607
1608        // The values that are used in a fullFormat string are specified
1609        // by position.
1610        return formatter.format(fullFormat, timeString, startWeekDayString, dateString);
1611    }
1612
1613    /**
1614     * Formats a date or a time according to the local conventions. There are
1615     * lots of options that allow the caller to control, for example, if the
1616     * time is shown, if the day of the week is shown, if the month name is
1617     * abbreviated, if noon is shown instead of 12pm, and so on. For the
1618     * complete list of options, see the documentation for
1619     * {@link #formatDateRange}.
1620     * <p>
1621     * Example output strings (date formats in these examples are shown using
1622     * the US date format convention but that may change depending on the
1623     * local settings):
1624     * <ul>
1625     *   <li>10:15am</li>
1626     *   <li>3:00pm</li>
1627     *   <li>3pm</li>
1628     *   <li>3PM</li>
1629     *   <li>08:00</li>
1630     *   <li>17:00</li>
1631     *   <li>noon</li>
1632     *   <li>Noon</li>
1633     *   <li>midnight</li>
1634     *   <li>Midnight</li>
1635     *   <li>Oct 31</li>
1636     *   <li>Oct 31, 2007</li>
1637     *   <li>October 31, 2007</li>
1638     *   <li>10am, Oct 31</li>
1639     *   <li>17:00, Oct 31</li>
1640     *   <li>Wed</li>
1641     *   <li>Wednesday</li>
1642     *   <li>10am, Wed, Oct 31</li>
1643     *   <li>Wed, Oct 31</li>
1644     *   <li>Wednesday, Oct 31</li>
1645     *   <li>Wed, Oct 31, 2007</li>
1646     *   <li>Wed, October 31</li>
1647     *   <li>10/31/2007</li>
1648     * </ul>
1649     *
1650     * @param context the context is required only if the time is shown
1651     * @param millis a point in time in UTC milliseconds
1652     * @param flags a bit mask of formatting options
1653     * @return a string containing the formatted date/time.
1654     */
1655    public static String formatDateTime(Context context, long millis, int flags) {
1656        return formatDateRange(context, millis, millis, flags);
1657    }
1658
1659    /**
1660     * @return a relative time string to display the time expressed by millis.  Times
1661     * are counted starting at midnight, which means that assuming that the current
1662     * time is March 31st, 0:30:
1663     * <ul>
1664     *   <li>"millis=0:10 today" will be displayed as "0:10"</li>
1665     *   <li>"millis=11:30pm the day before" will be displayed as "Mar 30"</li>
1666     * </ul>
1667     * If the given millis is in a different year, then the full date is
1668     * returned in numeric format (e.g., "10/12/2008").
1669     *
1670     * @param withPreposition If true, the string returned will include the correct
1671     * preposition ("at 9:20am", "on 10/12/2008" or "on May 29").
1672     */
1673    public static CharSequence getRelativeTimeSpanString(Context c, long millis,
1674            boolean withPreposition) {
1675
1676        String result;
1677        long now = System.currentTimeMillis();
1678        long span = now - millis;
1679
1680        synchronized (DateUtils.class) {
1681            if (sNowTime == null) {
1682                sNowTime = new Time();
1683            }
1684
1685            if (sThenTime == null) {
1686                sThenTime = new Time();
1687            }
1688
1689            sNowTime.set(now);
1690            sThenTime.set(millis);
1691
1692            int prepositionId;
1693            if (span < DAY_IN_MILLIS && sNowTime.weekDay == sThenTime.weekDay) {
1694                // Same day
1695                int flags = FORMAT_SHOW_TIME;
1696                result = formatDateRange(c, millis, millis, flags);
1697                prepositionId = R.string.preposition_for_time;
1698            } else if (sNowTime.year != sThenTime.year) {
1699                // Different years
1700                int flags = FORMAT_SHOW_DATE | FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE;
1701                result = formatDateRange(c, millis, millis, flags);
1702
1703                // This is a date (like "10/31/2008" so use the date preposition)
1704                prepositionId = R.string.preposition_for_date;
1705            } else {
1706                // Default
1707                int flags = FORMAT_SHOW_DATE | FORMAT_ABBREV_MONTH;
1708                result = formatDateRange(c, millis, millis, flags);
1709                prepositionId = R.string.preposition_for_date;
1710            }
1711            if (withPreposition) {
1712                Resources res = c.getResources();
1713                result = res.getString(prepositionId, result);
1714            }
1715        }
1716        return result;
1717    }
1718
1719    /**
1720     * Convenience function to return relative time string without preposition.
1721     * @param c context for resources
1722     * @param millis time in milliseconds
1723     * @return {@link CharSequence} containing relative time.
1724     * @see #getRelativeTimeSpanString(Context, long, boolean)
1725     */
1726    public static CharSequence getRelativeTimeSpanString(Context c, long millis) {
1727        return getRelativeTimeSpanString(c, millis, false /* no preposition */);
1728    }
1729
1730    private static Time sNowTime;
1731    private static Time sThenTime;
1732}
1733