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