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