DateUtils.java revision 4b294d6c96c4865b64338e5d222ff9dc87bad5b9
145b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov/* 245b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov * Copyright (C) 2010 The Android Open Source Project 345b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov * 445b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov * Licensed under the Apache License, Version 2.0 (the "License"); 545b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov * you may not use this file except in compliance with the License. 645b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov * You may obtain a copy of the License at 745b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov * 845b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov * http://www.apache.org/licenses/LICENSE-2.0 945b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov * 1045b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov * Unless required by applicable law or agreed to in writing, software 1145b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov * distributed under the License is distributed on an "AS IS" BASIS, 1245b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1345b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov * See the License for the specific language governing permissions and 1445b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov * limitations under the License. 1545b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov */ 1645b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov 1745b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikovpackage com.android.contacts.util; 1845b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov 1945b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikovimport android.content.Context; 2045b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikovimport android.text.format.DateFormat; 2145b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov 22428f008513d1591cc08fcfe2cf0c9237fb313241Chiao Chengimport com.android.contacts.common.util.CommonDateUtils; 23428f008513d1591cc08fcfe2cf0c9237fb313241Chiao Cheng 2445b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikovimport java.text.ParsePosition; 2545b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikovimport java.text.SimpleDateFormat; 265a7a26962cc781d675280c790789e2dea28a7b34Daniel Lehmannimport java.util.Calendar; 2745b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikovimport java.util.Date; 284b648485346462dc26c3a469caeae8589f5fbd58Daniel Lehmannimport java.util.Locale; 294b648485346462dc26c3a469caeae8589f5fbd58Daniel Lehmannimport java.util.TimeZone; 3045b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov 3145b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov/** 3245b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov * Utility methods for processing dates. 3345b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov */ 3445b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikovpublic class DateUtils { 354b648485346462dc26c3a469caeae8589f5fbd58Daniel Lehmann public static final TimeZone UTC_TIMEZONE = TimeZone.getTimeZone("UTC"); 364b648485346462dc26c3a469caeae8589f5fbd58Daniel Lehmann 375a7a26962cc781d675280c790789e2dea28a7b34Daniel Lehmann /** 385a7a26962cc781d675280c790789e2dea28a7b34Daniel Lehmann * When parsing a date without a year, the system assumes 1970, which wasn't a leap-year. 395a7a26962cc781d675280c790789e2dea28a7b34Daniel Lehmann * Let's add a one-off hack for that day of the year 405a7a26962cc781d675280c790789e2dea28a7b34Daniel Lehmann */ 415a7a26962cc781d675280c790789e2dea28a7b34Daniel Lehmann public static final String NO_YEAR_DATE_FEB29TH = "--02-29"; 4245b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov 432b3f95cc12b76523410782d4178562ce241410efDmitri Plotnikov // Variations of ISO 8601 date format. Do not change the order - it does affect the 442b3f95cc12b76523410782d4178562ce241410efDmitri Plotnikov // result in ambiguous cases. 4545b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov private static final SimpleDateFormat[] DATE_FORMATS = { 46428f008513d1591cc08fcfe2cf0c9237fb313241Chiao Cheng CommonDateUtils.FULL_DATE_FORMAT, 47428f008513d1591cc08fcfe2cf0c9237fb313241Chiao Cheng CommonDateUtils.DATE_AND_TIME_FORMAT, 484b648485346462dc26c3a469caeae8589f5fbd58Daniel Lehmann new SimpleDateFormat("yyyy-MM-dd'T'HH:mm'Z'", Locale.US), 494b648485346462dc26c3a469caeae8589f5fbd58Daniel Lehmann new SimpleDateFormat("yyyyMMdd", Locale.US), 504b648485346462dc26c3a469caeae8589f5fbd58Daniel Lehmann new SimpleDateFormat("yyyyMMdd'T'HHmmssSSS'Z'", Locale.US), 514b648485346462dc26c3a469caeae8589f5fbd58Daniel Lehmann new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'", Locale.US), 524b648485346462dc26c3a469caeae8589f5fbd58Daniel Lehmann new SimpleDateFormat("yyyyMMdd'T'HHmm'Z'", Locale.US), 5345b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov }; 542b3f95cc12b76523410782d4178562ce241410efDmitri Plotnikov 554b648485346462dc26c3a469caeae8589f5fbd58Daniel Lehmann static { 564b648485346462dc26c3a469caeae8589f5fbd58Daniel Lehmann for (SimpleDateFormat format : DATE_FORMATS) { 574b648485346462dc26c3a469caeae8589f5fbd58Daniel Lehmann format.setLenient(true); 584b648485346462dc26c3a469caeae8589f5fbd58Daniel Lehmann format.setTimeZone(UTC_TIMEZONE); 594b648485346462dc26c3a469caeae8589f5fbd58Daniel Lehmann } 60428f008513d1591cc08fcfe2cf0c9237fb313241Chiao Cheng CommonDateUtils.NO_YEAR_DATE_FORMAT.setTimeZone(UTC_TIMEZONE); 614b648485346462dc26c3a469caeae8589f5fbd58Daniel Lehmann } 624b648485346462dc26c3a469caeae8589f5fbd58Daniel Lehmann 6345b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov /** 6445b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov * Parses the supplied string to see if it looks like a date. If so, 652b3f95cc12b76523410782d4178562ce241410efDmitri Plotnikov * returns the date. Otherwise, returns null. 662b3f95cc12b76523410782d4178562ce241410efDmitri Plotnikov */ 672b3f95cc12b76523410782d4178562ce241410efDmitri Plotnikov public static Date parseDate(String string) { 682b3f95cc12b76523410782d4178562ce241410efDmitri Plotnikov ParsePosition parsePosition = new ParsePosition(0); 692b3f95cc12b76523410782d4178562ce241410efDmitri Plotnikov for (int i = 0; i < DATE_FORMATS.length; i++) { 702b3f95cc12b76523410782d4178562ce241410efDmitri Plotnikov SimpleDateFormat f = DATE_FORMATS[i]; 712b3f95cc12b76523410782d4178562ce241410efDmitri Plotnikov synchronized (f) { 722b3f95cc12b76523410782d4178562ce241410efDmitri Plotnikov parsePosition.setIndex(0); 732b3f95cc12b76523410782d4178562ce241410efDmitri Plotnikov Date date = f.parse(string, parsePosition); 742b3f95cc12b76523410782d4178562ce241410efDmitri Plotnikov if (parsePosition.getIndex() == string.length()) { 752b3f95cc12b76523410782d4178562ce241410efDmitri Plotnikov return date; 762b3f95cc12b76523410782d4178562ce241410efDmitri Plotnikov } 772b3f95cc12b76523410782d4178562ce241410efDmitri Plotnikov } 782b3f95cc12b76523410782d4178562ce241410efDmitri Plotnikov } 792b3f95cc12b76523410782d4178562ce241410efDmitri Plotnikov return null; 802b3f95cc12b76523410782d4178562ce241410efDmitri Plotnikov } 812b3f95cc12b76523410782d4178562ce241410efDmitri Plotnikov 825a7a26962cc781d675280c790789e2dea28a7b34Daniel Lehmann private static final Date getUtcDate(int year, int month, int dayOfMonth) { 835a7a26962cc781d675280c790789e2dea28a7b34Daniel Lehmann final Calendar calendar = Calendar.getInstance(UTC_TIMEZONE, Locale.US); 845a7a26962cc781d675280c790789e2dea28a7b34Daniel Lehmann calendar.set(Calendar.YEAR, year); 855a7a26962cc781d675280c790789e2dea28a7b34Daniel Lehmann calendar.set(Calendar.MONTH, month); 865a7a26962cc781d675280c790789e2dea28a7b34Daniel Lehmann calendar.set(Calendar.DAY_OF_MONTH, dayOfMonth); 875a7a26962cc781d675280c790789e2dea28a7b34Daniel Lehmann return calendar.getTime(); 885a7a26962cc781d675280c790789e2dea28a7b34Daniel Lehmann } 895a7a26962cc781d675280c790789e2dea28a7b34Daniel Lehmann 902b3f95cc12b76523410782d4178562ce241410efDmitri Plotnikov /** 914b294d6c96c4865b64338e5d222ff9dc87bad5b9Yorke Lee * Same as {@link #formatDate(Context context, String string, boolean longForm)}, with 924b294d6c96c4865b64338e5d222ff9dc87bad5b9Yorke Lee * longForm set to {@code true} by default. 934b294d6c96c4865b64338e5d222ff9dc87bad5b9Yorke Lee * 944b294d6c96c4865b64338e5d222ff9dc87bad5b9Yorke Lee * @param context Valid context 954b294d6c96c4865b64338e5d222ff9dc87bad5b9Yorke Lee * @param string String representation of a date to parse 964b294d6c96c4865b64338e5d222ff9dc87bad5b9Yorke Lee * @return Returns the same date in a cleaned up format. If the supplied string does not look 974b294d6c96c4865b64338e5d222ff9dc87bad5b9Yorke Lee * like a date, return it unchanged. 9845b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov */ 994b294d6c96c4865b64338e5d222ff9dc87bad5b9Yorke Lee 10045b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov public static String formatDate(Context context, String string) { 1014b294d6c96c4865b64338e5d222ff9dc87bad5b9Yorke Lee return formatDate(context, string, true); 1024b294d6c96c4865b64338e5d222ff9dc87bad5b9Yorke Lee } 1034b294d6c96c4865b64338e5d222ff9dc87bad5b9Yorke Lee 1044b294d6c96c4865b64338e5d222ff9dc87bad5b9Yorke Lee /** 1054b294d6c96c4865b64338e5d222ff9dc87bad5b9Yorke Lee * Parses the supplied string to see if it looks like a date. 1064b294d6c96c4865b64338e5d222ff9dc87bad5b9Yorke Lee * 1074b294d6c96c4865b64338e5d222ff9dc87bad5b9Yorke Lee * @param context Valid context 1084b294d6c96c4865b64338e5d222ff9dc87bad5b9Yorke Lee * @param string String representation of a date to parse 1094b294d6c96c4865b64338e5d222ff9dc87bad5b9Yorke Lee * @param longForm If true, return the date formatted into its long string representation. 1104b294d6c96c4865b64338e5d222ff9dc87bad5b9Yorke Lee * If false, return the date formatted using its short form representation (i.e. 12/11/2012) 1114b294d6c96c4865b64338e5d222ff9dc87bad5b9Yorke Lee * @return Returns the same date in a cleaned up format. If the supplied string does not look 1124b294d6c96c4865b64338e5d222ff9dc87bad5b9Yorke Lee * like a date, return it unchanged. 1134b294d6c96c4865b64338e5d222ff9dc87bad5b9Yorke Lee */ 1144b294d6c96c4865b64338e5d222ff9dc87bad5b9Yorke Lee public static String formatDate(Context context, String string, boolean longForm) { 11545b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov if (string == null) { 11645b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov return null; 11745b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov } 11845b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov 11945b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov string = string.trim(); 12045b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov if (string.length() == 0) { 12145b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov return string; 12245b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov } 12345b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov 12445b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov ParsePosition parsePosition = new ParsePosition(0); 12545b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov 1265a7a26962cc781d675280c790789e2dea28a7b34Daniel Lehmann final boolean noYearParsed; 1272b3f95cc12b76523410782d4178562ce241410efDmitri Plotnikov Date date; 1282b3f95cc12b76523410782d4178562ce241410efDmitri Plotnikov 1295a7a26962cc781d675280c790789e2dea28a7b34Daniel Lehmann // Unfortunately, we can't parse Feb 29th correctly, so let's handle this day seperately 1305a7a26962cc781d675280c790789e2dea28a7b34Daniel Lehmann if (NO_YEAR_DATE_FEB29TH.equals(string)) { 1315a7a26962cc781d675280c790789e2dea28a7b34Daniel Lehmann date = getUtcDate(0, Calendar.FEBRUARY, 29); 1325a7a26962cc781d675280c790789e2dea28a7b34Daniel Lehmann noYearParsed = true; 1335a7a26962cc781d675280c790789e2dea28a7b34Daniel Lehmann } else { 134428f008513d1591cc08fcfe2cf0c9237fb313241Chiao Cheng synchronized (CommonDateUtils.NO_YEAR_DATE_FORMAT) { 135428f008513d1591cc08fcfe2cf0c9237fb313241Chiao Cheng date = CommonDateUtils.NO_YEAR_DATE_FORMAT.parse(string, parsePosition); 1365a7a26962cc781d675280c790789e2dea28a7b34Daniel Lehmann } 1375a7a26962cc781d675280c790789e2dea28a7b34Daniel Lehmann noYearParsed = parsePosition.getIndex() == string.length(); 1382b3f95cc12b76523410782d4178562ce241410efDmitri Plotnikov } 1392b3f95cc12b76523410782d4178562ce241410efDmitri Plotnikov 1405a7a26962cc781d675280c790789e2dea28a7b34Daniel Lehmann if (noYearParsed) { 1414b294d6c96c4865b64338e5d222ff9dc87bad5b9Yorke Lee final java.text.DateFormat outFormat = getLocalizedDateFormatWithoutYear(context); 1422b3f95cc12b76523410782d4178562ce241410efDmitri Plotnikov synchronized (outFormat) { 1434b294d6c96c4865b64338e5d222ff9dc87bad5b9Yorke Lee outFormat.setTimeZone(UTC_TIMEZONE); 1442b3f95cc12b76523410782d4178562ce241410efDmitri Plotnikov return outFormat.format(date); 1452b3f95cc12b76523410782d4178562ce241410efDmitri Plotnikov } 1462b3f95cc12b76523410782d4178562ce241410efDmitri Plotnikov } 1472b3f95cc12b76523410782d4178562ce241410efDmitri Plotnikov 14845b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov for (int i = 0; i < DATE_FORMATS.length; i++) { 14945b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov SimpleDateFormat f = DATE_FORMATS[i]; 15045b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov synchronized (f) { 15145b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov parsePosition.setIndex(0); 1522b3f95cc12b76523410782d4178562ce241410efDmitri Plotnikov date = f.parse(string, parsePosition); 15345b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov if (parsePosition.getIndex() == string.length()) { 1544b294d6c96c4865b64338e5d222ff9dc87bad5b9Yorke Lee final java.text.DateFormat outFormat = 1554b294d6c96c4865b64338e5d222ff9dc87bad5b9Yorke Lee longForm ? DateFormat.getLongDateFormat(context) : 1564b294d6c96c4865b64338e5d222ff9dc87bad5b9Yorke Lee DateFormat.getDateFormat(context); 1574b294d6c96c4865b64338e5d222ff9dc87bad5b9Yorke Lee outFormat.setTimeZone(UTC_TIMEZONE); 1584b294d6c96c4865b64338e5d222ff9dc87bad5b9Yorke Lee return outFormat.format(date); 15945b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov } 16045b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov } 16145b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov } 16245b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov return string; 16345b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov } 16445b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov 16511812c59ee140d62eaf3a8d826d2018767d490c5Daniel Lehmann public static boolean isMonthBeforeDay(Context context) { 16645b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov char[] dateFormatOrder = DateFormat.getDateFormatOrder(context); 16745b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov for (int i = 0; i < dateFormatOrder.length; i++) { 16845b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov if (dateFormatOrder[i] == DateFormat.DATE) { 16945b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov return false; 17045b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov } 17145b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov if (dateFormatOrder[i] == DateFormat.MONTH) { 17245b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov return true; 17345b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov } 17445b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov } 17545b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov return false; 17645b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov } 177c7ea6e965339cd7a6936f418d729ac92ac7eaf79Yorke Lee 178c7ea6e965339cd7a6936f418d729ac92ac7eaf79Yorke Lee /** 179c7ea6e965339cd7a6936f418d729ac92ac7eaf79Yorke Lee * Returns a SimpleDateFormat object without the year fields by using a regular expression 180c7ea6e965339cd7a6936f418d729ac92ac7eaf79Yorke Lee * to eliminate the year in the string pattern. In the rare occurence that the resulting 181c7ea6e965339cd7a6936f418d729ac92ac7eaf79Yorke Lee * pattern cannot be reconverted into a SimpleDateFormat, it uses the provided context to 182c7ea6e965339cd7a6936f418d729ac92ac7eaf79Yorke Lee * determine whether the month field should be displayed before the day field, and returns 183c7ea6e965339cd7a6936f418d729ac92ac7eaf79Yorke Lee * either "MMMM dd" or "dd MMMM" converted into a SimpleDateFormat. 184c7ea6e965339cd7a6936f418d729ac92ac7eaf79Yorke Lee */ 1854b294d6c96c4865b64338e5d222ff9dc87bad5b9Yorke Lee public static java.text.DateFormat getLocalizedDateFormatWithoutYear(Context context) { 186c7ea6e965339cd7a6936f418d729ac92ac7eaf79Yorke Lee final String pattern = ((SimpleDateFormat) SimpleDateFormat.getDateInstance( 187c7ea6e965339cd7a6936f418d729ac92ac7eaf79Yorke Lee java.text.DateFormat.LONG)).toPattern(); 188c7ea6e965339cd7a6936f418d729ac92ac7eaf79Yorke Lee // Determine the correct regex pattern for year. 189c7ea6e965339cd7a6936f418d729ac92ac7eaf79Yorke Lee // Special case handling for Spanish locale by checking for "de" 190c7ea6e965339cd7a6936f418d729ac92ac7eaf79Yorke Lee final String yearPattern = pattern.contains( 191c7ea6e965339cd7a6936f418d729ac92ac7eaf79Yorke Lee "de") ? "[^Mm]*[Yy]+[^Mm]*" : "[^DdMm]*[Yy]+[^DdMm]*"; 192c7ea6e965339cd7a6936f418d729ac92ac7eaf79Yorke Lee try { 193c7ea6e965339cd7a6936f418d729ac92ac7eaf79Yorke Lee // Eliminate the substring in pattern that matches the format for that of year 194c7ea6e965339cd7a6936f418d729ac92ac7eaf79Yorke Lee return new SimpleDateFormat(pattern.replaceAll(yearPattern, "")); 195c7ea6e965339cd7a6936f418d729ac92ac7eaf79Yorke Lee } catch (IllegalArgumentException e) { 196c7ea6e965339cd7a6936f418d729ac92ac7eaf79Yorke Lee return new SimpleDateFormat( 197c7ea6e965339cd7a6936f418d729ac92ac7eaf79Yorke Lee DateUtils.isMonthBeforeDay(context) ? "MMMM dd" : "dd MMMM"); 198c7ea6e965339cd7a6936f418d729ac92ac7eaf79Yorke Lee } 199c7ea6e965339cd7a6936f418d729ac92ac7eaf79Yorke Lee } 20045b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov} 201