| /* |
| * Copyright (C) 2006 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package android.text.format; |
| |
| import android.content.Context; |
| import android.provider.Settings; |
| import android.text.SpannableStringBuilder; |
| import android.text.Spanned; |
| import android.text.SpannedString; |
| |
| import com.android.internal.R; |
| |
| import java.util.Calendar; |
| import java.util.Date; |
| import java.util.GregorianCalendar; |
| import java.util.Locale; |
| import java.util.TimeZone; |
| import java.text.SimpleDateFormat; |
| |
| import libcore.icu.LocaleData; |
| |
| /** |
| Utility class for producing strings with formatted date/time. |
| |
| <p> |
| Most callers should avoid supplying their own format strings to this |
| class' {@code format} methods and rely on the correctly localized ones |
| supplied by the system. This class' factory methods return |
| appropriately-localized {@link java.text.DateFormat} instances, suitable |
| for both formatting and parsing dates. For the canonical documentation |
| of format strings, see {@link java.text.SimpleDateFormat}. |
| </p> |
| <p> |
| The format methods in this class takes as inputs a format string and a representation of a date/time. |
| The format string controls how the output is generated. |
| This class only supports a subset of the full Unicode specification. |
| 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 'M' 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 -> 9<br/> |
| MM -> 09<br/> |
| MMM -> Sep<br/> |
| MMMM -> 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 -> 7<br/> |
| mm -> 07<br/> |
| mmm -> 007<br/> |
| mmmm -> 0007 |
| </p> |
| <p> |
| Examples for April 6, 1970 at 3:23am:<br/> |
| "MM/dd/yy h:mmaa" -> "04/06/70 3:23am"<br/> |
| "MMM dd, yyyy h:mmaa" -> "Apr 6, 1970 3:23am"<br/> |
| "MMMM dd, yyyy h:mmaa" -> "April 6, 1970 3:23am"<br/> |
| "E, MMMM dd, yyyy h:mmaa" -> "Mon, April 6, 1970 3:23am&<br/> |
| "EEEE, MMMM dd, yyyy h:mmaa" -> "Monday, April 6, 1970 3:23am"<br/> |
| "'Noteworthy day: 'M/d/yy" -> "Noteworthy day: 4/6/70" |
| */ |
| |
| public class DateFormat { |
| /** |
| Text in the format string that should be copied verbatim rather that |
| 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 = '\''; |
| |
| /** |
| This designator indicates whether the <code>HOUR</code> field is before |
| or after noon. The output is lower-case. |
| |
| Examples: |
| a -> a or p |
| aa -> am or pm |
| */ |
| public static final char AM_PM = 'a'; |
| |
| /** |
| This designator indicates whether the <code>HOUR</code> field is before |
| or after noon. The output is capitalized. |
| |
| Examples: |
| A -> A or P |
| AA -> AM or PM |
| */ |
| public static final char CAPITAL_AM_PM = 'A'; |
| |
| /** |
| This designator indicates the day of the month. |
| |
| Examples for the 9th of the month: |
| d -> 9 |
| dd -> 09 |
| */ |
| public static final char DATE = 'd'; |
| |
| /** |
| This designator indicates the name of the day of the week. |
| |
| Examples for Sunday: |
| E -> Sun |
| EEEE -> Sunday |
| */ |
| public static final char DAY = 'E'; |
| |
| /** |
| This designator indicates the hour of the day in 12 hour format. |
| |
| Examples for 3pm: |
| h -> 3 |
| hh -> 03 |
| */ |
| public static final char HOUR = 'h'; |
| |
| /** |
| This designator indicates the hour of the day in 24 hour format. |
| |
| Example for 3pm: |
| k -> 15 |
| |
| Examples for midnight: |
| k -> 0 |
| kk -> 00 |
| */ |
| public static final char HOUR_OF_DAY = 'k'; |
| |
| /** |
| This designator indicates the minute of the hour. |
| |
| Examples for 7 minutes past the hour: |
| m -> 7 |
| mm -> 07 |
| */ |
| public static final char MINUTE = 'm'; |
| |
| /** |
| This designator indicates the month of the year. See also |
| {@link #STANDALONE_MONTH}. |
| |
| Examples for September: |
| M -> 9 |
| MM -> 09 |
| MMM -> Sep |
| MMMM -> September |
| */ |
| public static final char MONTH = 'M'; |
| |
| /** |
| This designator indicates the standalone month of the year, |
| 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'; |
| |
| /** |
| This designator indicates the seconds of the minute. |
| |
| Examples for 7 seconds past the minute: |
| s -> 7 |
| ss -> 07 |
| */ |
| public static final char SECONDS = 's'; |
| |
| /** |
| This designator indicates the offset of the timezone from GMT. |
| |
| Example for US/Pacific timezone: |
| z -> -0800 |
| zz -> PST |
| */ |
| public static final char TIME_ZONE = 'z'; |
| |
| /** |
| This designator indicates the year. |
| |
| Examples for 2006 |
| y -> 06 |
| yyyy -> 2006 |
| */ |
| public static final char YEAR = 'y'; |
| |
| |
| private static final Object sLocaleLock = new Object(); |
| private static Locale sIs24HourLocale; |
| private static boolean sIs24Hour; |
| |
| |
| /** |
| * Returns true if user preference is set to 24-hour format. |
| * @param context the context to use for the content resolver |
| * @return true if 24 hour time format is selected, false otherwise. |
| */ |
| public static boolean is24HourFormat(Context context) { |
| String value = Settings.System.getString(context.getContentResolver(), |
| Settings.System.TIME_12_24); |
| |
| if (value == null) { |
| Locale locale = context.getResources().getConfiguration().locale; |
| |
| synchronized (sLocaleLock) { |
| if (sIs24HourLocale != null && sIs24HourLocale.equals(locale)) { |
| return sIs24Hour; |
| } |
| } |
| |
| java.text.DateFormat natural = |
| java.text.DateFormat.getTimeInstance( |
| java.text.DateFormat.LONG, locale); |
| |
| if (natural instanceof SimpleDateFormat) { |
| SimpleDateFormat sdf = (SimpleDateFormat) natural; |
| String pattern = sdf.toPattern(); |
| |
| if (pattern.indexOf('H') >= 0) { |
| value = "24"; |
| } else { |
| value = "12"; |
| } |
| } else { |
| value = "12"; |
| } |
| |
| synchronized (sLocaleLock) { |
| sIs24HourLocale = locale; |
| sIs24Hour = value.equals("24"); |
| } |
| |
| return sIs24Hour; |
| } |
| |
| return value.equals("24"); |
| } |
| |
| /** |
| * Returns a {@link java.text.DateFormat} object that can format the time according |
| * to the current locale and the user's 12-/24-hour clock preference. |
| * @param context the application context |
| * @return the {@link java.text.DateFormat} object that properly formats the time. |
| */ |
| public static java.text.DateFormat getTimeFormat(Context context) { |
| LocaleData d = LocaleData.get(context.getResources().getConfiguration().locale); |
| boolean is24 = is24HourFormat(context); |
| return new java.text.SimpleDateFormat(is24 ? d.timeFormat24 : d.timeFormat12); |
| } |
| |
| /** |
| * Returns a {@link java.text.DateFormat} object that can format the date |
| * in short form (such as 12/31/1999) according |
| * to the current locale and the user's date-order preference. |
| * @param context the application context |
| * @return the {@link java.text.DateFormat} object that properly formats the date. |
| */ |
| public static java.text.DateFormat getDateFormat(Context context) { |
| String value = Settings.System.getString(context.getContentResolver(), |
| Settings.System.DATE_FORMAT); |
| |
| return getDateFormatForSetting(context, value); |
| } |
| |
| /** |
| * Returns a {@link java.text.DateFormat} object to format the date |
| * as if the date format setting were set to <code>value</code>, |
| * including null to use the locale's default format. |
| * @param context the application context |
| * @param value the date format setting string to interpret for |
| * the current locale |
| * @hide |
| */ |
| public static java.text.DateFormat getDateFormatForSetting(Context context, |
| String value) { |
| String format = getDateFormatStringForSetting(context, value); |
| |
| return new java.text.SimpleDateFormat(format); |
| } |
| |
| private static String getDateFormatStringForSetting(Context context, String value) { |
| if (value != null) { |
| int month = value.indexOf('M'); |
| int day = value.indexOf('d'); |
| int year = value.indexOf('y'); |
| |
| if (month >= 0 && day >= 0 && year >= 0) { |
| String template = context.getString(R.string.numeric_date_template); |
| if (year < month && year < day) { |
| if (month < day) { |
| value = String.format(template, "yyyy", "MM", "dd"); |
| } else { |
| value = String.format(template, "yyyy", "dd", "MM"); |
| } |
| } else if (month < day) { |
| if (day < year) { |
| value = String.format(template, "MM", "dd", "yyyy"); |
| } else { // unlikely |
| value = String.format(template, "MM", "yyyy", "dd"); |
| } |
| } else { // day < month |
| if (month < year) { |
| value = String.format(template, "dd", "MM", "yyyy"); |
| } else { // unlikely |
| value = String.format(template, "dd", "yyyy", "MM"); |
| } |
| } |
| |
| return value; |
| } |
| } |
| |
| /* |
| * The setting is not set; use the default. |
| * We use a resource string here instead of just DateFormat.SHORT |
| * so that we get a four-digit year instead a two-digit year. |
| */ |
| value = context.getString(R.string.numeric_date_format); |
| return value; |
| } |
| |
| /** |
| * Returns a {@link java.text.DateFormat} object that can format the date |
| * in long form (such as December 31, 1999) for the current locale. |
| * @param context the application context |
| * @return the {@link java.text.DateFormat} object that formats the date in long form. |
| */ |
| public static java.text.DateFormat getLongDateFormat(Context context) { |
| return java.text.DateFormat.getDateInstance(java.text.DateFormat.LONG); |
| } |
| |
| /** |
| * Returns a {@link java.text.DateFormat} object that can format the date |
| * in medium form (such as Dec. 31, 1999) for the current locale. |
| * @param context the application context |
| * @return the {@link java.text.DateFormat} object that formats the date in long form. |
| */ |
| public static java.text.DateFormat getMediumDateFormat(Context context) { |
| return java.text.DateFormat.getDateInstance(java.text.DateFormat.MEDIUM); |
| } |
| |
| /** |
| * 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 |
| * specified by the user's format preference. Note that this order is |
| * only appropriate for all-numeric dates; spelled-out (MEDIUM and LONG) |
| * dates will generally contain other punctuation, spaces, or words, |
| * not just the day, month, and year, and not necessarily in the same |
| * order returned here. |
| */ |
| public static char[] getDateFormatOrder(Context context) { |
| char[] order = new char[] {DATE, MONTH, YEAR}; |
| String value = getDateFormatString(context); |
| int index = 0; |
| boolean foundDate = false; |
| boolean foundMonth = false; |
| boolean foundYear = false; |
| |
| for (char c : value.toCharArray()) { |
| if (!foundDate && (c == DATE)) { |
| foundDate = true; |
| order[index] = DATE; |
| index++; |
| } |
| |
| if (!foundMonth && (c == MONTH || c == STANDALONE_MONTH)) { |
| foundMonth = true; |
| order[index] = MONTH; |
| index++; |
| } |
| |
| if (!foundYear && (c == YEAR)) { |
| foundYear = true; |
| order[index] = YEAR; |
| index++; |
| } |
| } |
| return order; |
| } |
| |
| private static String getDateFormatString(Context context) { |
| String value = Settings.System.getString(context.getContentResolver(), |
| Settings.System.DATE_FORMAT); |
| |
| return getDateFormatStringForSetting(context, value); |
| } |
| |
| /** |
| * Given a format string and a time in milliseconds since Jan 1, 1970 GMT, returns a |
| * CharSequence containing the requested date. |
| * @param inFormat the format string, as described in {@link android.text.format.DateFormat} |
| * @param inTimeInMillis in milliseconds since Jan 1, 1970 GMT |
| * @return a {@link CharSequence} containing the requested text |
| */ |
| public static CharSequence format(CharSequence inFormat, long inTimeInMillis) { |
| return format(inFormat, new Date(inTimeInMillis)); |
| } |
| |
| /** |
| * Given a format string and a {@link java.util.Date} object, returns a CharSequence containing |
| * the requested date. |
| * @param inFormat the format string, as described in {@link android.text.format.DateFormat} |
| * @param inDate the date to format |
| * @return a {@link CharSequence} containing the requested text |
| */ |
| public static CharSequence format(CharSequence inFormat, Date inDate) { |
| Calendar c = new GregorianCalendar(); |
| |
| c.setTime(inDate); |
| |
| return format(inFormat, c); |
| } |
| |
| /** |
| * Indicates whether the specified format string contains seconds. |
| * |
| * Always returns false if the input format is null. |
| * |
| * @param inFormat the format string, as described in {@link android.text.format.DateFormat} |
| * |
| * @return true if the format string contains {@link #SECONDS}, false otherwise |
| * |
| * @hide |
| */ |
| public static boolean hasSeconds(CharSequence inFormat) { |
| if (inFormat == null) return false; |
| |
| final int length = inFormat.length(); |
| |
| int c; |
| int count; |
| |
| for (int i = 0; i < length; i += count) { |
| count = 1; |
| c = inFormat.charAt(i); |
| |
| if (c == QUOTE) { |
| count = skipQuotedText(inFormat, i, length); |
| } else if (c == SECONDS) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| private static int skipQuotedText(CharSequence s, int i, int len) { |
| if (i + 1 < len && s.charAt(i + 1) == QUOTE) { |
| return 2; |
| } |
| |
| int count = 1; |
| // skip leading quote |
| i++; |
| |
| while (i < len) { |
| char c = s.charAt(i); |
| |
| if (c == QUOTE) { |
| count++; |
| // QUOTEQUOTE -> QUOTE |
| if (i + 1 < len && s.charAt(i + 1) == QUOTE) { |
| i++; |
| } else { |
| break; |
| } |
| } else { |
| i++; |
| count++; |
| } |
| } |
| |
| return count; |
| } |
| |
| /** |
| * Given a format string and a {@link java.util.Calendar} object, returns a CharSequence |
| * containing the requested date. |
| * @param inFormat the format string, as described in {@link android.text.format.DateFormat} |
| * @param inDate the date to format |
| * @return a {@link CharSequence} containing the requested text |
| */ |
| public static CharSequence format(CharSequence inFormat, Calendar inDate) { |
| SpannableStringBuilder s = new SpannableStringBuilder(inFormat); |
| int c; |
| int count; |
| |
| int len = inFormat.length(); |
| |
| for (int i = 0; i < len; i += count) { |
| int temp; |
| |
| count = 1; |
| c = s.charAt(i); |
| |
| if (c == QUOTE) { |
| count = appendQuotedText(s, i, len); |
| len = s.length(); |
| continue; |
| } |
| |
| while ((i + count < len) && (s.charAt(i + count) == c)) { |
| count++; |
| } |
| |
| String replacement; |
| |
| switch (c) { |
| case AM_PM: |
| replacement = DateUtils.getAMPMString(inDate.get(Calendar.AM_PM)); |
| break; |
| |
| 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); |
| break; |
| |
| case DAY: |
| temp = inDate.get(Calendar.DAY_OF_WEEK); |
| replacement = DateUtils.getDayOfWeekString(temp, |
| count < 4 ? |
| DateUtils.LENGTH_MEDIUM : |
| DateUtils.LENGTH_LONG); |
| break; |
| |
| case HOUR: |
| temp = inDate.get(Calendar.HOUR); |
| |
| if (0 == temp) |
| temp = 12; |
| |
| replacement = zeroPad(temp, count); |
| break; |
| |
| case HOUR_OF_DAY: |
| replacement = zeroPad(inDate.get(Calendar.HOUR_OF_DAY), count); |
| break; |
| |
| case MINUTE: |
| replacement = zeroPad(inDate.get(Calendar.MINUTE), count); |
| break; |
| |
| case MONTH: |
| case STANDALONE_MONTH: |
| replacement = getMonthString(inDate, count, c); |
| break; |
| |
| case SECONDS: |
| replacement = zeroPad(inDate.get(Calendar.SECOND), count); |
| break; |
| |
| case TIME_ZONE: |
| replacement = getTimeZoneString(inDate, count); |
| break; |
| |
| case YEAR: |
| replacement = getYearString(inDate, count); |
| break; |
| |
| default: |
| replacement = null; |
| break; |
| } |
| |
| if (replacement != null) { |
| s.replace(i, i + count, replacement); |
| count = replacement.length(); // CARE: count is used in the for loop above |
| len = s.length(); |
| } |
| } |
| |
| if (inFormat instanceof Spanned) |
| return new SpannedString(s); |
| else |
| return s.toString(); |
| } |
| |
| private static String getMonthString(Calendar inDate, int count, int kind) { |
| boolean standalone = (kind == STANDALONE_MONTH); |
| int month = inDate.get(Calendar.MONTH); |
| |
| if (count >= 4) { |
| return standalone |
| ? DateUtils.getStandaloneMonthString(month, DateUtils.LENGTH_LONG) |
| : DateUtils.getMonthString(month, DateUtils.LENGTH_LONG); |
| } else if (count == 3) { |
| return standalone |
| ? DateUtils.getStandaloneMonthString(month, DateUtils.LENGTH_MEDIUM) |
| : DateUtils.getMonthString(month, DateUtils.LENGTH_MEDIUM); |
| } else { |
| // Calendar.JANUARY == 0, so add 1 to month. |
| return zeroPad(month+1, count); |
| } |
| } |
| |
| private static String getTimeZoneString(Calendar inDate, int count) { |
| TimeZone tz = inDate.getTimeZone(); |
| |
| if (count < 2) { // FIXME: shouldn't this be <= 2 ? |
| return formatZoneOffset(inDate.get(Calendar.DST_OFFSET) + |
| inDate.get(Calendar.ZONE_OFFSET), |
| count); |
| } else { |
| boolean dst = inDate.get(Calendar.DST_OFFSET) != 0; |
| return tz.getDisplayName(dst, TimeZone.SHORT); |
| } |
| } |
| |
| private static String formatZoneOffset(int offset, int count) { |
| offset /= 1000; // milliseconds to seconds |
| StringBuilder tb = new StringBuilder(); |
| |
| if (offset < 0) { |
| tb.insert(0, "-"); |
| offset = -offset; |
| } else { |
| tb.insert(0, "+"); |
| } |
| |
| int hours = offset / 3600; |
| int minutes = (offset % 3600) / 60; |
| |
| tb.append(zeroPad(hours, 2)); |
| tb.append(zeroPad(minutes, 2)); |
| return tb.toString(); |
| } |
| |
| private static String getYearString(Calendar inDate, int count) { |
| int year = inDate.get(Calendar.YEAR); |
| return (count <= 2) ? zeroPad(year % 100, 2) |
| : String.format(Locale.getDefault(), "%d", year); |
| } |
| |
| private static int appendQuotedText(SpannableStringBuilder s, int i, int len) { |
| if (i + 1 < len && s.charAt(i + 1) == QUOTE) { |
| s.delete(i, i + 1); |
| return 1; |
| } |
| |
| int count = 0; |
| |
| // delete leading quote |
| s.delete(i, i + 1); |
| len--; |
| |
| while (i < len) { |
| char c = s.charAt(i); |
| |
| if (c == QUOTE) { |
| // QUOTEQUOTE -> QUOTE |
| if (i + 1 < len && s.charAt(i + 1) == QUOTE) { |
| |
| s.delete(i, i + 1); |
| len--; |
| count++; |
| i++; |
| } else { |
| // Closing QUOTE ends quoted text copying |
| s.delete(i, i + 1); |
| break; |
| } |
| } else { |
| i++; |
| count++; |
| } |
| } |
| |
| return count; |
| } |
| |
| private static String zeroPad(int inValue, int inMinDigits) { |
| return String.format(Locale.getDefault(), "%0" + inMinDigits + "d", inValue); |
| } |
| } |