| /* |
| * 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.pim; |
| |
| import android.content.Context; |
| import android.provider.Settings; |
| import android.provider.Settings.SettingNotFoundException; |
| import android.text.SpannableStringBuilder; |
| import android.text.Spanned; |
| import android.text.SpannedString; |
| |
| import java.util.Calendar; |
| import java.util.Date; |
| import java.util.GregorianCalendar; |
| import java.util.TimeZone; |
| |
| /** |
| Utility class for producing strings with formatted date/time. |
| |
| <p> |
| This class takes as inputs a format string and a representation of a date/time. |
| The format string controls how the output is generated. |
| </p> |
| <p> |
| 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/> |
| "'Best day evar: 'M/d/yy" -> "Best day evar: 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 |
| |
| Examples for September: |
| M -> 9 |
| MM -> 09 |
| MMM -> Sep |
| MMMM -> September |
| */ |
| public static final char MONTH = 'M'; |
| |
| /** |
| 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'; |
| |
| /** |
| * @return true if the user has set the system to use a 24 hour time |
| * format, else false. |
| */ |
| public static boolean is24HourFormat(Context context) { |
| String value = Settings.System.getString(context.getContentResolver(), |
| Settings.System.TIME_12_24); |
| boolean b24 = !(value == null || value.equals("12")); |
| return b24; |
| } |
| |
| /** |
| * Returns a {@link java.text.DateFormat} object that can format the time according |
| * to the current user preference. |
| * @param context the application context |
| * @return the {@link java.text.DateFormat} object that properly formats the time. |
| */ |
| public static final java.text.DateFormat getTimeFormat(Context context) { |
| boolean b24 = is24HourFormat(context); |
| return new java.text.SimpleDateFormat(b24 ? "H:mm" : "h:mm a"); |
| } |
| |
| /** |
| * Returns a {@link java.text.DateFormat} object that can format the date according |
| * to the current user preference. |
| * @param context the application context |
| * @return the {@link java.text.DateFormat} object that properly formats the date. |
| */ |
| public static final java.text.DateFormat getDateFormat(Context context) { |
| String value = getDateFormatString(context); |
| return new java.text.SimpleDateFormat(value); |
| } |
| |
| /** |
| * Returns a {@link java.text.DateFormat} object that can format the date |
| * in long form (such as December 31, 1999) based on user preference. |
| * @param context the application context |
| * @return the {@link java.text.DateFormat} object that formats the date in long form. |
| */ |
| public static final java.text.DateFormat getLongDateFormat(Context context) { |
| String value = getDateFormatString(context); |
| if (value.indexOf('M') < value.indexOf('d')) { |
| value = "MMMM dd, yyyy"; |
| } else { |
| value = "dd MMMM, yyyy"; |
| } |
| return new java.text.SimpleDateFormat(value); |
| } |
| |
| /** |
| * 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 |
| * preferred by the user. |
| */ |
| public static final 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)) { |
| 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); |
| if (value == null || value.length() < 6) { |
| value = "MM-dd-yyyy"; |
| } |
| return value; |
| } |
| |
| public static final CharSequence format(CharSequence inFormat, long inTimeInMillis) { |
| return format(inFormat, new Date(inTimeInMillis)); |
| } |
| |
| public static final CharSequence format(CharSequence inFormat, Date inDate) { |
| Calendar c = new GregorianCalendar(); |
| |
| c.setTime(inDate); |
| |
| return format(inFormat, c); |
| } |
| |
| public static final 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: |
| replacement = getMonthString(inDate, count); |
| 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 final String getMonthString(Calendar inDate, int count) { |
| int month = inDate.get(Calendar.MONTH); |
| |
| if (count >= 4) |
| return DateUtils.getMonthString(month, DateUtils.LENGTH_LONG); |
| else if (count == 3) |
| return DateUtils.getMonthString(month, DateUtils.LENGTH_MEDIUM); |
| else { |
| // Calendar.JANUARY == 0, so add 1 to month. |
| return zeroPad(month+1, count); |
| } |
| } |
| |
| private static final 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 final 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 final String getYearString(Calendar inDate, int count) { |
| int year = inDate.get(Calendar.YEAR); |
| return (count <= 2) ? zeroPad(year % 100, 2) : String.valueOf(year); |
| } |
| |
| private static final 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 final String zeroPad(int inValue, int inMinDigits) { |
| String val = String.valueOf(inValue); |
| |
| if (val.length() < inMinDigits) { |
| char[] buf = new char[inMinDigits]; |
| |
| for (int i = 0; i < inMinDigits; i++) |
| buf[i] = '0'; |
| |
| val.getChars(0, val.length(), buf, inMinDigits - val.length()); |
| val = new String(buf); |
| } |
| return val; |
| } |
| } |