Merge "Improve DateFormat.format."

This commit is contained in:
Elliott Hughes
2013-03-08 23:50:02 +00:00
committed by Gerrit Code Review
3 changed files with 127 additions and 260 deletions

View File

@@ -22276,19 +22276,19 @@ package android.text.format {
method public static java.text.DateFormat getMediumDateFormat(android.content.Context); method public static java.text.DateFormat getMediumDateFormat(android.content.Context);
method public static java.text.DateFormat getTimeFormat(android.content.Context); method public static java.text.DateFormat getTimeFormat(android.content.Context);
method public static boolean is24HourFormat(android.content.Context); method public static boolean is24HourFormat(android.content.Context);
field public static final char AM_PM = 97; // 0x0061 'a' field public static final deprecated char AM_PM = 97; // 0x0061 'a'
field public static final char CAPITAL_AM_PM = 65; // 0x0041 'A' field public static final deprecated char CAPITAL_AM_PM = 65; // 0x0041 'A'
field public static final char DATE = 100; // 0x0064 'd' field public static final deprecated char DATE = 100; // 0x0064 'd'
field public static final char DAY = 69; // 0x0045 'E' field public static final deprecated char DAY = 69; // 0x0045 'E'
field public static final char HOUR = 104; // 0x0068 'h' field public static final deprecated char HOUR = 104; // 0x0068 'h'
field public static final char HOUR_OF_DAY = 107; // 0x006b 'k' field public static final deprecated char HOUR_OF_DAY = 107; // 0x006b 'k'
field public static final char MINUTE = 109; // 0x006d 'm' field public static final deprecated char MINUTE = 109; // 0x006d 'm'
field public static final char MONTH = 77; // 0x004d 'M' field public static final deprecated char MONTH = 77; // 0x004d 'M'
field public static final char QUOTE = 39; // 0x0027 '\'' field public static final deprecated char QUOTE = 39; // 0x0027 '\''
field public static final char SECONDS = 115; // 0x0073 's' field public static final deprecated char SECONDS = 115; // 0x0073 's'
field public static final char STANDALONE_MONTH = 76; // 0x004c 'L' field public static final deprecated char STANDALONE_MONTH = 76; // 0x004c 'L'
field public static final char TIME_ZONE = 122; // 0x007a 'z' field public static final deprecated char TIME_ZONE = 122; // 0x007a 'z'
field public static final char YEAR = 121; // 0x0079 'y' field public static final deprecated char YEAR = 121; // 0x0079 'y'
} }
public class DateUtils { public class DateUtils {

View File

@@ -34,178 +34,72 @@ import java.text.SimpleDateFormat;
import libcore.icu.LocaleData; import libcore.icu.LocaleData;
/** /**
Utility class for producing strings with formatted date/time. * Utility class for producing strings with formatted date/time.
*
<p> * <p>Most callers should avoid supplying their own format strings to this
Most callers should avoid supplying their own format strings to this * class' {@code format} methods and rely on the correctly localized ones
class' {@code format} methods and rely on the correctly localized ones * supplied by the system. This class' factory methods return
supplied by the system. This class' factory methods return * appropriately-localized {@link java.text.DateFormat} instances, suitable
appropriately-localized {@link java.text.DateFormat} instances, suitable * for both formatting and parsing dates. For the canonical documentation
for both formatting and parsing dates. For the canonical documentation * of format strings, see {@link java.text.SimpleDateFormat}.
of format strings, see {@link java.text.SimpleDateFormat}. *
</p> * <p>The format methods in this class implement a subset of Unicode
<p> * <a href="http://www.unicode.org/reports/tr35/#Date_Format_Patterns">UTS #35</a> patterns.
The format methods in this class takes as inputs a format string and a representation of a date/time. * The subset supported by this class includes the following format characters:
The format string controls how the output is generated. * {@code acdEhkLMmsyz}. See {@link java.text.SimpleDateFormat} for more documentation
This class only supports a subset of the full Unicode specification. * about patterns, or if you need a more compete implementation.
Use {@link java.text.SimpleDateFormat} if you need more.
Formatting characters may be repeated in order to get more detailed representations
of that field. For instance, the format character &apos;M&apos; is used to
represent the month. Depending on how many times that character is repeated
you get a different representation.
</p>
<p>
For the month of September:<br/>
M -&gt; 9<br/>
MM -&gt; 09<br/>
MMM -&gt; Sep<br/>
MMMM -&gt; September
</p>
<p>
The effects of the duplication vary depending on the nature of the field.
See the notes on the individual field formatters for details. For purely numeric
fields such as <code>HOUR</code> adding more copies of the designator will
zero-pad the value to that number of characters.
</p>
<p>
For 7 minutes past the hour:<br/>
m -&gt; 7<br/>
mm -&gt; 07<br/>
mmm -&gt; 007<br/>
mmmm -&gt; 0007
</p>
<p>
Examples for April 6, 1970 at 3:23am:<br/>
&quot;MM/dd/yy h:mmaa&quot; -&gt; &quot;04/06/70 3:23am&quot<br/>
&quot;MMM dd, yyyy h:mmaa&quot; -&gt; &quot;Apr 6, 1970 3:23am&quot<br/>
&quot;MMMM dd, yyyy h:mmaa&quot; -&gt; &quot;April 6, 1970 3:23am&quot<br/>
&quot;E, MMMM dd, yyyy h:mmaa&quot; -&gt; &quot;Mon, April 6, 1970 3:23am&<br/>
&quot;EEEE, MMMM dd, yyyy h:mmaa&quot; -&gt; &quot;Monday, April 6, 1970 3:23am&quot;<br/>
&quot;&apos;Noteworthy day: &apos;M/d/yy&quot; -&gt; &quot;Noteworthy day: 4/6/70&quot;
*/ */
public class DateFormat { public class DateFormat {
/** /** @deprecated Use a literal {@code '} instead. */
Text in the format string that should be copied verbatim rather that @Deprecated
interpreted as formatting codes must be surrounded by the <code>QUOTE</code>
character. If you need to embed a literal <code>QUOTE</code> character in
the output text then use two in a row.
*/
public static final char QUOTE = '\''; public static final char QUOTE = '\'';
/** /** @deprecated Use a literal {@code 'a'} instead. */
This designator indicates whether the <code>HOUR</code> field is before @Deprecated
or after noon. The output is lower-case.
Examples:
a -> a or p
aa -> am or pm
*/
public static final char AM_PM = 'a'; public static final char AM_PM = 'a';
/** /** @deprecated Use a literal {@code 'a'} instead; 'A' was always equivalent to 'a'. */
This designator indicates whether the <code>HOUR</code> field is before @Deprecated
or after noon. The output is capitalized.
Examples:
A -> A or P
AA -> AM or PM
*/
public static final char CAPITAL_AM_PM = 'A'; public static final char CAPITAL_AM_PM = 'A';
/** /** @deprecated Use a literal {@code 'd'} instead. */
This designator indicates the day of the month. @Deprecated
Examples for the 9th of the month:
d -> 9
dd -> 09
*/
public static final char DATE = 'd'; public static final char DATE = 'd';
/** /** @deprecated Use a literal {@code 'E'} instead. */
This designator indicates the name of the day of the week. @Deprecated
Examples for Sunday:
E -> Sun
EEEE -> Sunday
*/
public static final char DAY = 'E'; public static final char DAY = 'E';
/** /** @deprecated Use a literal {@code 'h'} instead. */
This designator indicates the hour of the day in 12 hour format. @Deprecated
Examples for 3pm:
h -> 3
hh -> 03
*/
public static final char HOUR = 'h'; public static final char HOUR = 'h';
/** /** @deprecated Use a literal {@code 'k'} instead. */
This designator indicates the hour of the day in 24 hour format. @Deprecated
Example for 3pm:
k -> 15
Examples for midnight:
k -> 0
kk -> 00
*/
public static final char HOUR_OF_DAY = 'k'; public static final char HOUR_OF_DAY = 'k';
/** /** @deprecated Use a literal {@code 'm'} instead. */
This designator indicates the minute of the hour. @Deprecated
Examples for 7 minutes past the hour:
m -> 7
mm -> 07
*/
public static final char MINUTE = 'm'; public static final char MINUTE = 'm';
/** /** @deprecated Use a literal {@code 'M'} instead. */
This designator indicates the month of the year. See also @Deprecated
{@link #STANDALONE_MONTH}.
Examples for September:
M -> 9
MM -> 09
MMM -> Sep
MMMM -> September
*/
public static final char MONTH = 'M'; public static final char MONTH = 'M';
/** /** @deprecated Use a literal {@code 'L'} instead. */
This designator indicates the standalone month of the year, @Deprecated
necessary in some format strings in some languages. For
example, Russian distinguishes between the "June" in
"June" and that in "June 2010".
*/
public static final char STANDALONE_MONTH = 'L'; public static final char STANDALONE_MONTH = 'L';
/** /** @deprecated Use a literal {@code 's'} instead. */
This designator indicates the seconds of the minute. @Deprecated
Examples for 7 seconds past the minute:
s -> 7
ss -> 07
*/
public static final char SECONDS = 's'; public static final char SECONDS = 's';
/** /** @deprecated Use a literal {@code 'z'} instead. */
This designator indicates the offset of the timezone from GMT. @Deprecated
Example for US/Pacific timezone:
z -> -0800
zz -> PST
*/
public static final char TIME_ZONE = 'z'; public static final char TIME_ZONE = 'z';
/** /** @deprecated Use a literal {@code 'y'} instead. */
This designator indicates the year. @Deprecated
Examples for 2006
y -> 06
yyyy -> 2006
*/
public static final char YEAR = 'y'; public static final char YEAR = 'y';
@@ -233,8 +127,7 @@ public class DateFormat {
} }
java.text.DateFormat natural = java.text.DateFormat natural =
java.text.DateFormat.getTimeInstance( java.text.DateFormat.getTimeInstance(java.text.DateFormat.LONG, locale);
java.text.DateFormat.LONG, locale);
if (natural instanceof SimpleDateFormat) { if (natural instanceof SimpleDateFormat) {
SimpleDateFormat sdf = (SimpleDateFormat) natural; SimpleDateFormat sdf = (SimpleDateFormat) natural;
@@ -273,7 +166,7 @@ public class DateFormat {
} }
/** /**
* Returns a {@link java.text.DateFormat} object that can format the date * Returns a {@link java.text.DateFormat} object that can format the date
* in short form (such as 12/31/1999) according * in short form (such as 12/31/1999) according
* to the current locale and the user's date-order preference. * to the current locale and the user's date-order preference.
* @param context the application context * @param context the application context
@@ -342,10 +235,10 @@ public class DateFormat {
value = context.getString(R.string.numeric_date_format); value = context.getString(R.string.numeric_date_format);
return value; return value;
} }
/** /**
* Returns a {@link java.text.DateFormat} object that can format the date * Returns a {@link java.text.DateFormat} object that can format the date
* in long form (such as December 31, 1999) for the current locale. * in long form (such as {@code Monday, January 3, 2000}) for the current locale.
* @param context the application context * @param context the application context
* @return the {@link java.text.DateFormat} object that formats the date in long form. * @return the {@link java.text.DateFormat} object that formats the date in long form.
*/ */
@@ -355,7 +248,7 @@ public class DateFormat {
/** /**
* Returns a {@link java.text.DateFormat} object that can format the date * Returns a {@link java.text.DateFormat} object that can format the date
* in medium form (such as Dec. 31, 1999) for the current locale. * in medium form (such as {@code Jan 3, 2000}) for the current locale.
* @param context the application context * @param context the application context
* @return the {@link java.text.DateFormat} object that formats the date in long form. * @return the {@link java.text.DateFormat} object that formats the date in long form.
*/ */
@@ -365,13 +258,13 @@ public class DateFormat {
/** /**
* Gets the current date format stored as a char array. The array will contain * Gets the current date format stored as a char array. The array will contain
* 3 elements ({@link #DATE}, {@link #MONTH}, and {@link #YEAR}) in the order * 3 elements ({@link #DATE}, {@link #MONTH}, and {@link #YEAR}) in the order
* specified by the user's format preference. Note that this order is * specified by the user's format preference. Note that this order is
* only appropriate for all-numeric dates; spelled-out (MEDIUM and LONG) * only appropriate for all-numeric dates; spelled-out (MEDIUM and LONG)
* dates will generally contain other punctuation, spaces, or words, * dates will generally contain other punctuation, spaces, or words,
* not just the day, month, and year, and not necessarily in the same * not just the day, month, and year, and not necessarily in the same
* order returned here. * order returned here.
*/ */
public static char[] getDateFormatOrder(Context context) { public static char[] getDateFormatOrder(Context context) {
char[] order = new char[] {DATE, MONTH, YEAR}; char[] order = new char[] {DATE, MONTH, YEAR};
String value = getDateFormatString(context); String value = getDateFormatString(context);
@@ -401,7 +294,7 @@ public class DateFormat {
} }
return order; return order;
} }
private static String getDateFormatString(Context context) { private static String getDateFormatString(Context context) {
String value = Settings.System.getString(context.getContentResolver(), String value = Settings.System.getString(context.getContentResolver(),
Settings.System.DATE_FORMAT); Settings.System.DATE_FORMAT);
@@ -410,7 +303,7 @@ public class DateFormat {
} }
/** /**
* Given a format string and a time in milliseconds since Jan 1, 1970 GMT, returns a * Given a format string and a time in milliseconds since Jan 1, 1970 GMT, returns a
* CharSequence containing the requested date. * CharSequence containing the requested date.
* @param inFormat the format string, as described in {@link android.text.format.DateFormat} * @param inFormat the format string, as described in {@link android.text.format.DateFormat}
* @param inTimeInMillis in milliseconds since Jan 1, 1970 GMT * @param inTimeInMillis in milliseconds since Jan 1, 1970 GMT
@@ -428,22 +321,20 @@ public class DateFormat {
* @return a {@link CharSequence} containing the requested text * @return a {@link CharSequence} containing the requested text
*/ */
public static CharSequence format(CharSequence inFormat, Date inDate) { public static CharSequence format(CharSequence inFormat, Date inDate) {
Calendar c = new GregorianCalendar(); Calendar c = new GregorianCalendar();
c.setTime(inDate); c.setTime(inDate);
return format(inFormat, c); return format(inFormat, c);
} }
/** /**
* Indicates whether the specified format string contains seconds. * Indicates whether the specified format string contains seconds.
* *
* Always returns false if the input format is null. * Always returns false if the input format is null.
* *
* @param inFormat the format string, as described in {@link android.text.format.DateFormat} * @param inFormat the format string, as described in {@link android.text.format.DateFormat}
* *
* @return true if the format string contains {@link #SECONDS}, false otherwise * @return true if the format string contains {@link #SECONDS}, false otherwise
* *
* @hide * @hide
*/ */
public static boolean hasSeconds(CharSequence inFormat) { public static boolean hasSeconds(CharSequence inFormat) {
@@ -498,24 +389,23 @@ public class DateFormat {
} }
/** /**
* Given a format string and a {@link java.util.Calendar} object, returns a CharSequence * Given a format string and a {@link java.util.Calendar} object, returns a CharSequence
* containing the requested date. * containing the requested date.
* @param inFormat the format string, as described in {@link android.text.format.DateFormat} * @param inFormat the format string, as described in {@link android.text.format.DateFormat}
* @param inDate the date to format * @param inDate the date to format
* @return a {@link CharSequence} containing the requested text * @return a {@link CharSequence} containing the requested text
*/ */
public static CharSequence format(CharSequence inFormat, Calendar inDate) { public static CharSequence format(CharSequence inFormat, Calendar inDate) {
SpannableStringBuilder s = new SpannableStringBuilder(inFormat); SpannableStringBuilder s = new SpannableStringBuilder(inFormat);
int c; int count;
int count;
LocaleData localeData = LocaleData.get(Locale.getDefault());
int len = inFormat.length(); int len = inFormat.length();
for (int i = 0; i < len; i += count) { for (int i = 0; i < len; i += count) {
int temp;
count = 1; count = 1;
c = s.charAt(i); int c = s.charAt(i);
if (c == QUOTE) { if (c == QUOTE) {
count = appendQuotedText(s, i, len); count = appendQuotedText(s, i, len);
@@ -528,102 +418,89 @@ public class DateFormat {
} }
String replacement; String replacement;
switch (c) { switch (c) {
case AM_PM: case 'A':
replacement = DateUtils.getAMPMString(inDate.get(Calendar.AM_PM)); case 'a':
replacement = localeData.amPm[inDate.get(Calendar.AM_PM) - Calendar.AM];
break; break;
case 'd':
case CAPITAL_AM_PM:
//FIXME: this is the same as AM_PM? no capital?
replacement = DateUtils.getAMPMString(inDate.get(Calendar.AM_PM));
break;
case DATE:
replacement = zeroPad(inDate.get(Calendar.DATE), count); replacement = zeroPad(inDate.get(Calendar.DATE), count);
break; break;
case 'c':
case DAY: case 'E':
temp = inDate.get(Calendar.DAY_OF_WEEK); replacement = getDayOfWeekString(localeData,
replacement = DateUtils.getDayOfWeekString(temp, inDate.get(Calendar.DAY_OF_WEEK), count, c);
count < 4 ?
DateUtils.LENGTH_MEDIUM :
DateUtils.LENGTH_LONG);
break; break;
case 'h':
case HOUR: int hour = inDate.get(Calendar.HOUR);
temp = inDate.get(Calendar.HOUR); replacement = zeroPad(hour == 0 ? 24 : hour, count);
if (0 == temp)
temp = 12;
replacement = zeroPad(temp, count);
break; break;
case 'k':
case HOUR_OF_DAY:
replacement = zeroPad(inDate.get(Calendar.HOUR_OF_DAY), count); replacement = zeroPad(inDate.get(Calendar.HOUR_OF_DAY), count);
break; break;
case 'L':
case MINUTE: case 'M':
replacement = getMonthString(localeData,
inDate.get(Calendar.MONTH), count, c);
break;
case 'm':
replacement = zeroPad(inDate.get(Calendar.MINUTE), count); replacement = zeroPad(inDate.get(Calendar.MINUTE), count);
break; break;
case 's':
case MONTH:
case STANDALONE_MONTH:
replacement = getMonthString(inDate, count, c);
break;
case SECONDS:
replacement = zeroPad(inDate.get(Calendar.SECOND), count); replacement = zeroPad(inDate.get(Calendar.SECOND), count);
break; break;
case 'y':
case TIME_ZONE: replacement = getYearString(inDate.get(Calendar.YEAR), count);
break;
case 'z':
replacement = getTimeZoneString(inDate, count); replacement = getTimeZoneString(inDate, count);
break; break;
case YEAR:
replacement = getYearString(inDate, count);
break;
default: default:
replacement = null; replacement = null;
break; break;
} }
if (replacement != null) { if (replacement != null) {
s.replace(i, i + count, replacement); s.replace(i, i + count, replacement);
count = replacement.length(); // CARE: count is used in the for loop above count = replacement.length(); // CARE: count is used in the for loop above
len = s.length(); len = s.length();
} }
} }
if (inFormat instanceof Spanned) if (inFormat instanceof Spanned) {
return new SpannedString(s); return new SpannedString(s);
else } else {
return s.toString(); return s.toString();
}
} }
private static String getMonthString(Calendar inDate, int count, int kind) { private static String getDayOfWeekString(LocaleData ld, int day, int count, int kind) {
boolean standalone = (kind == STANDALONE_MONTH); boolean standalone = (kind == 'c');
int month = inDate.get(Calendar.MONTH); if (count == 5) {
return standalone ? ld.tinyStandAloneWeekdayNames[day] : ld.tinyWeekdayNames[day];
if (count >= 4) { } else if (count == 4) {
return standalone return standalone ? ld.longStandAloneWeekdayNames[day] : ld.longWeekdayNames[day];
? DateUtils.getStandaloneMonthString(month, DateUtils.LENGTH_LONG) } else {
: DateUtils.getMonthString(month, DateUtils.LENGTH_LONG); return standalone ? ld.shortStandAloneWeekdayNames[day] : ld.shortWeekdayNames[day];
}
}
private static String getMonthString(LocaleData ld, int month, int count, int kind) {
boolean standalone = (kind == 'L');
if (count == 5) {
return standalone ? ld.tinyStandAloneMonthNames[month] : ld.tinyMonthNames[month];
} else if (count == 4) {
return standalone ? ld.longStandAloneMonthNames[month] : ld.longMonthNames[month];
} else if (count == 3) { } else if (count == 3) {
return standalone return standalone ? ld.shortStandAloneMonthNames[month] : ld.shortMonthNames[month];
? DateUtils.getStandaloneMonthString(month, DateUtils.LENGTH_MEDIUM)
: DateUtils.getMonthString(month, DateUtils.LENGTH_MEDIUM);
} else { } else {
// Calendar.JANUARY == 0, so add 1 to month. // Calendar.JANUARY == 0, so add 1 to month.
return zeroPad(month+1, count); return zeroPad(month+1, count);
} }
} }
private static String getTimeZoneString(Calendar inDate, int count) { private static String getTimeZoneString(Calendar inDate, int count) {
TimeZone tz = inDate.getTimeZone(); TimeZone tz = inDate.getTimeZone();
if (count < 2) { // FIXME: shouldn't this be <= 2 ? if (count < 2) { // FIXME: shouldn't this be <= 2 ?
return formatZoneOffset(inDate.get(Calendar.DST_OFFSET) + return formatZoneOffset(inDate.get(Calendar.DST_OFFSET) +
inDate.get(Calendar.ZONE_OFFSET), inDate.get(Calendar.ZONE_OFFSET),
@@ -652,13 +529,12 @@ public class DateFormat {
tb.append(zeroPad(minutes, 2)); tb.append(zeroPad(minutes, 2));
return tb.toString(); return tb.toString();
} }
private static String getYearString(Calendar inDate, int count) { private static String getYearString(int year, int count) {
int year = inDate.get(Calendar.YEAR);
return (count <= 2) ? zeroPad(year % 100, 2) return (count <= 2) ? zeroPad(year % 100, 2)
: String.format(Locale.getDefault(), "%d", year); : String.format(Locale.getDefault(), "%d", year);
} }
private static int appendQuotedText(SpannableStringBuilder s, int i, int len) { private static int appendQuotedText(SpannableStringBuilder s, int i, int len) {
if (i + 1 < len && s.charAt(i + 1) == QUOTE) { if (i + 1 < len && s.charAt(i + 1) == QUOTE) {
s.delete(i, i + 1); s.delete(i, i + 1);

View File

@@ -274,10 +274,6 @@ public class DateUtils
*/ */
@Deprecated @Deprecated
public static String getMonthString(int month, int abbrev) { public static String getMonthString(int month, int abbrev) {
// Note that here we use d.shortMonthNames for MEDIUM, SHORT and SHORTER.
// This is a shortcut to not spam the translators with too many variations
// of the same string. If we find that in a language the distinction
// is necessary, we can can add more without changing this API.
LocaleData d = LocaleData.get(Locale.getDefault()); LocaleData d = LocaleData.get(Locale.getDefault());
String[] names; String[] names;
switch (abbrev) { switch (abbrev) {
@@ -308,19 +304,14 @@ public class DateUtils
*/ */
@Deprecated @Deprecated
public static String getStandaloneMonthString(int month, int abbrev) { public static String getStandaloneMonthString(int month, int abbrev) {
// Note that here we use d.shortMonthNames for MEDIUM, SHORT and SHORTER.
// This is a shortcut to not spam the translators with too many variations
// of the same string. If we find that in a language the distinction
// is necessary, we can can add more without changing this API.
LocaleData d = LocaleData.get(Locale.getDefault()); LocaleData d = LocaleData.get(Locale.getDefault());
String[] names; String[] names;
switch (abbrev) { switch (abbrev) {
case LENGTH_LONG: names = d.longStandAloneMonthNames; case LENGTH_LONG: names = d.longStandAloneMonthNames; break;
break;
case LENGTH_MEDIUM: names = d.shortMonthNames; break; case LENGTH_MEDIUM: names = d.shortMonthNames; break;
case LENGTH_SHORT: names = d.shortMonthNames; break; case LENGTH_SHORT: names = d.shortMonthNames; break;
case LENGTH_SHORTER: names = d.shortMonthNames; break; case LENGTH_SHORTER: names = d.shortMonthNames; break;
case LENGTH_SHORTEST: names = d.tinyMonthNames; break; case LENGTH_SHORTEST: names = d.tinyStandAloneMonthNames; break;
default: names = d.shortMonthNames; break; default: names = d.shortMonthNames; break;
} }
return names[month]; return names[month];