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