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; 28ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Leeimport java.util.GregorianCalendar; 294b648485346462dc26c3a469caeae8589f5fbd58Daniel Lehmannimport java.util.Locale; 304b648485346462dc26c3a469caeae8589f5fbd58Daniel Lehmannimport java.util.TimeZone; 3145b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov 3245b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov/** 3345b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov * Utility methods for processing dates. 3445b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov */ 3545b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikovpublic class DateUtils { 364b648485346462dc26c3a469caeae8589f5fbd58Daniel Lehmann public static final TimeZone UTC_TIMEZONE = TimeZone.getTimeZone("UTC"); 374b648485346462dc26c3a469caeae8589f5fbd58Daniel Lehmann 385a7a26962cc781d675280c790789e2dea28a7b34Daniel Lehmann /** 395a7a26962cc781d675280c790789e2dea28a7b34Daniel Lehmann * When parsing a date without a year, the system assumes 1970, which wasn't a leap-year. 405a7a26962cc781d675280c790789e2dea28a7b34Daniel Lehmann * Let's add a one-off hack for that day of the year 415a7a26962cc781d675280c790789e2dea28a7b34Daniel Lehmann */ 425a7a26962cc781d675280c790789e2dea28a7b34Daniel Lehmann public static final String NO_YEAR_DATE_FEB29TH = "--02-29"; 4345b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov 442b3f95cc12b76523410782d4178562ce241410efDmitri Plotnikov // Variations of ISO 8601 date format. Do not change the order - it does affect the 452b3f95cc12b76523410782d4178562ce241410efDmitri Plotnikov // result in ambiguous cases. 4645b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov private static final SimpleDateFormat[] DATE_FORMATS = { 47428f008513d1591cc08fcfe2cf0c9237fb313241Chiao Cheng CommonDateUtils.FULL_DATE_FORMAT, 48428f008513d1591cc08fcfe2cf0c9237fb313241Chiao Cheng CommonDateUtils.DATE_AND_TIME_FORMAT, 494b648485346462dc26c3a469caeae8589f5fbd58Daniel Lehmann new SimpleDateFormat("yyyy-MM-dd'T'HH:mm'Z'", Locale.US), 504b648485346462dc26c3a469caeae8589f5fbd58Daniel Lehmann new SimpleDateFormat("yyyyMMdd", Locale.US), 514b648485346462dc26c3a469caeae8589f5fbd58Daniel Lehmann new SimpleDateFormat("yyyyMMdd'T'HHmmssSSS'Z'", Locale.US), 524b648485346462dc26c3a469caeae8589f5fbd58Daniel Lehmann new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'", Locale.US), 534b648485346462dc26c3a469caeae8589f5fbd58Daniel Lehmann new SimpleDateFormat("yyyyMMdd'T'HHmm'Z'", Locale.US), 5445b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov }; 552b3f95cc12b76523410782d4178562ce241410efDmitri Plotnikov 564b648485346462dc26c3a469caeae8589f5fbd58Daniel Lehmann static { 574b648485346462dc26c3a469caeae8589f5fbd58Daniel Lehmann for (SimpleDateFormat format : DATE_FORMATS) { 584b648485346462dc26c3a469caeae8589f5fbd58Daniel Lehmann format.setLenient(true); 594b648485346462dc26c3a469caeae8589f5fbd58Daniel Lehmann format.setTimeZone(UTC_TIMEZONE); 604b648485346462dc26c3a469caeae8589f5fbd58Daniel Lehmann } 61428f008513d1591cc08fcfe2cf0c9237fb313241Chiao Cheng CommonDateUtils.NO_YEAR_DATE_FORMAT.setTimeZone(UTC_TIMEZONE); 624b648485346462dc26c3a469caeae8589f5fbd58Daniel Lehmann } 634b648485346462dc26c3a469caeae8589f5fbd58Daniel Lehmann 6445b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov /** 65ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee * Parses the supplied string to see if it looks like a date. 66ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee * 67ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee * @param string The string representation of the provided date 68ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee * @param mustContainYear If true, the string is parsed as a date containing a year. If false, 69ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee * the string is parsed into a valid date even if the year field is missing. 70ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee * @return A Calendar object corresponding to the date if the string is successfully parsed. 71ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee * If not, null is returned. 722b3f95cc12b76523410782d4178562ce241410efDmitri Plotnikov */ 73ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee public static Calendar parseDate(String string, boolean mustContainYear) { 742b3f95cc12b76523410782d4178562ce241410efDmitri Plotnikov ParsePosition parsePosition = new ParsePosition(0); 75ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee Date date; 76ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee if (!mustContainYear) { 77ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee final boolean noYearParsed; 78ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee // Unfortunately, we can't parse Feb 29th correctly, so let's handle this day seperately 79ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee if (NO_YEAR_DATE_FEB29TH.equals(string)) { 80ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee return getUtcDate(0, Calendar.FEBRUARY, 29); 81ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee } else { 82ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee synchronized (CommonDateUtils.NO_YEAR_DATE_FORMAT) { 83ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee date = CommonDateUtils.NO_YEAR_DATE_FORMAT.parse(string, parsePosition); 84ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee } 85ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee noYearParsed = parsePosition.getIndex() == string.length(); 86ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee } 87ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee 88ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee if (noYearParsed) { 89ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee return getUtcDate(date, true); 90ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee } 91ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee } 922b3f95cc12b76523410782d4178562ce241410efDmitri Plotnikov for (int i = 0; i < DATE_FORMATS.length; i++) { 932b3f95cc12b76523410782d4178562ce241410efDmitri Plotnikov SimpleDateFormat f = DATE_FORMATS[i]; 942b3f95cc12b76523410782d4178562ce241410efDmitri Plotnikov synchronized (f) { 952b3f95cc12b76523410782d4178562ce241410efDmitri Plotnikov parsePosition.setIndex(0); 96ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee date = f.parse(string, parsePosition); 972b3f95cc12b76523410782d4178562ce241410efDmitri Plotnikov if (parsePosition.getIndex() == string.length()) { 98ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee return getUtcDate(date, false); 992b3f95cc12b76523410782d4178562ce241410efDmitri Plotnikov } 1002b3f95cc12b76523410782d4178562ce241410efDmitri Plotnikov } 1012b3f95cc12b76523410782d4178562ce241410efDmitri Plotnikov } 1022b3f95cc12b76523410782d4178562ce241410efDmitri Plotnikov return null; 1032b3f95cc12b76523410782d4178562ce241410efDmitri Plotnikov } 1042b3f95cc12b76523410782d4178562ce241410efDmitri Plotnikov 105ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee private static final Calendar getUtcDate(Date date, boolean noYear) { 1065a7a26962cc781d675280c790789e2dea28a7b34Daniel Lehmann final Calendar calendar = Calendar.getInstance(UTC_TIMEZONE, Locale.US); 107ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee calendar.setTime(date); 108ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee if (noYear) { 109ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee calendar.set(Calendar.YEAR, 0); 110ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee } 111ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee return calendar; 112ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee } 113ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee 114ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee private static final Calendar getUtcDate(int year, int month, int dayOfMonth) { 115ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee final Calendar calendar = Calendar.getInstance(UTC_TIMEZONE, Locale.US); 116ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee calendar.clear(); 1175a7a26962cc781d675280c790789e2dea28a7b34Daniel Lehmann calendar.set(Calendar.YEAR, year); 1185a7a26962cc781d675280c790789e2dea28a7b34Daniel Lehmann calendar.set(Calendar.MONTH, month); 1195a7a26962cc781d675280c790789e2dea28a7b34Daniel Lehmann calendar.set(Calendar.DAY_OF_MONTH, dayOfMonth); 120ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee return calendar; 121ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee } 122ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee 123ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee public static boolean isYearSet(Calendar cal) { 124ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee // use the Calendar.YEAR field to track whether or not the year is set instead of 125ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee // Calendar.isSet() because doing Calendar.get() causes Calendar.isSet() to become 126ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee // true irregardless of what the previous value was 127ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee return cal.get(Calendar.YEAR) > 1; 1285a7a26962cc781d675280c790789e2dea28a7b34Daniel Lehmann } 1295a7a26962cc781d675280c790789e2dea28a7b34Daniel Lehmann 1302b3f95cc12b76523410782d4178562ce241410efDmitri Plotnikov /** 1314b294d6c96c4865b64338e5d222ff9dc87bad5b9Yorke Lee * Same as {@link #formatDate(Context context, String string, boolean longForm)}, with 1324b294d6c96c4865b64338e5d222ff9dc87bad5b9Yorke Lee * longForm set to {@code true} by default. 1334b294d6c96c4865b64338e5d222ff9dc87bad5b9Yorke Lee * 1344b294d6c96c4865b64338e5d222ff9dc87bad5b9Yorke Lee * @param context Valid context 1354b294d6c96c4865b64338e5d222ff9dc87bad5b9Yorke Lee * @param string String representation of a date to parse 1364b294d6c96c4865b64338e5d222ff9dc87bad5b9Yorke Lee * @return Returns the same date in a cleaned up format. If the supplied string does not look 1374b294d6c96c4865b64338e5d222ff9dc87bad5b9Yorke Lee * like a date, return it unchanged. 13845b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov */ 1394b294d6c96c4865b64338e5d222ff9dc87bad5b9Yorke Lee 14045b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov public static String formatDate(Context context, String string) { 1414b294d6c96c4865b64338e5d222ff9dc87bad5b9Yorke Lee return formatDate(context, string, true); 1424b294d6c96c4865b64338e5d222ff9dc87bad5b9Yorke Lee } 1434b294d6c96c4865b64338e5d222ff9dc87bad5b9Yorke Lee 1444b294d6c96c4865b64338e5d222ff9dc87bad5b9Yorke Lee /** 1454b294d6c96c4865b64338e5d222ff9dc87bad5b9Yorke Lee * Parses the supplied string to see if it looks like a date. 1464b294d6c96c4865b64338e5d222ff9dc87bad5b9Yorke Lee * 1474b294d6c96c4865b64338e5d222ff9dc87bad5b9Yorke Lee * @param context Valid context 1484b294d6c96c4865b64338e5d222ff9dc87bad5b9Yorke Lee * @param string String representation of a date to parse 1494b294d6c96c4865b64338e5d222ff9dc87bad5b9Yorke Lee * @param longForm If true, return the date formatted into its long string representation. 1504b294d6c96c4865b64338e5d222ff9dc87bad5b9Yorke Lee * If false, return the date formatted using its short form representation (i.e. 12/11/2012) 1514b294d6c96c4865b64338e5d222ff9dc87bad5b9Yorke Lee * @return Returns the same date in a cleaned up format. If the supplied string does not look 1524b294d6c96c4865b64338e5d222ff9dc87bad5b9Yorke Lee * like a date, return it unchanged. 1534b294d6c96c4865b64338e5d222ff9dc87bad5b9Yorke Lee */ 1544b294d6c96c4865b64338e5d222ff9dc87bad5b9Yorke Lee public static String formatDate(Context context, String string, boolean longForm) { 15545b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov if (string == null) { 15645b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov return null; 15745b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov } 15845b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov 15945b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov string = string.trim(); 16045b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov if (string.length() == 0) { 16145b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov return string; 16245b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov } 163ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee final Calendar cal = parseDate(string, false); 16445b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov 165ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee // we weren't able to parse the string successfully so just return it unchanged 166ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee if (cal == null) { 167ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee return string; 1682b3f95cc12b76523410782d4178562ce241410efDmitri Plotnikov } 1692b3f95cc12b76523410782d4178562ce241410efDmitri Plotnikov 170ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee final boolean isYearSet = isYearSet(cal); 171ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee final java.text.DateFormat outFormat; 172ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee if (!isYearSet) { 173ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee outFormat = getLocalizedDateFormatWithoutYear(context); 174ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee } else { 175ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee outFormat = 176ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee longForm ? DateFormat.getLongDateFormat(context) : 177ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee DateFormat.getDateFormat(context); 1782b3f95cc12b76523410782d4178562ce241410efDmitri Plotnikov } 179ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee synchronized (outFormat) { 180ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee outFormat.setTimeZone(UTC_TIMEZONE); 181ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee return outFormat.format(cal.getTime()); 18245b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov } 18345b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov } 18445b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov 18511812c59ee140d62eaf3a8d826d2018767d490c5Daniel Lehmann public static boolean isMonthBeforeDay(Context context) { 18645b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov char[] dateFormatOrder = DateFormat.getDateFormatOrder(context); 18745b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov for (int i = 0; i < dateFormatOrder.length; i++) { 18845b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov if (dateFormatOrder[i] == DateFormat.DATE) { 18945b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov return false; 19045b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov } 19145b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov if (dateFormatOrder[i] == DateFormat.MONTH) { 19245b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov return true; 19345b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov } 19445b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov } 19545b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov return false; 19645b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov } 197c7ea6e965339cd7a6936f418d729ac92ac7eaf79Yorke Lee 198c7ea6e965339cd7a6936f418d729ac92ac7eaf79Yorke Lee /** 199c7ea6e965339cd7a6936f418d729ac92ac7eaf79Yorke Lee * Returns a SimpleDateFormat object without the year fields by using a regular expression 200c7ea6e965339cd7a6936f418d729ac92ac7eaf79Yorke Lee * to eliminate the year in the string pattern. In the rare occurence that the resulting 201c7ea6e965339cd7a6936f418d729ac92ac7eaf79Yorke Lee * pattern cannot be reconverted into a SimpleDateFormat, it uses the provided context to 202c7ea6e965339cd7a6936f418d729ac92ac7eaf79Yorke Lee * determine whether the month field should be displayed before the day field, and returns 203c7ea6e965339cd7a6936f418d729ac92ac7eaf79Yorke Lee * either "MMMM dd" or "dd MMMM" converted into a SimpleDateFormat. 204c7ea6e965339cd7a6936f418d729ac92ac7eaf79Yorke Lee */ 2054b294d6c96c4865b64338e5d222ff9dc87bad5b9Yorke Lee public static java.text.DateFormat getLocalizedDateFormatWithoutYear(Context context) { 206c7ea6e965339cd7a6936f418d729ac92ac7eaf79Yorke Lee final String pattern = ((SimpleDateFormat) SimpleDateFormat.getDateInstance( 207c7ea6e965339cd7a6936f418d729ac92ac7eaf79Yorke Lee java.text.DateFormat.LONG)).toPattern(); 208c7ea6e965339cd7a6936f418d729ac92ac7eaf79Yorke Lee // Determine the correct regex pattern for year. 209c7ea6e965339cd7a6936f418d729ac92ac7eaf79Yorke Lee // Special case handling for Spanish locale by checking for "de" 210c7ea6e965339cd7a6936f418d729ac92ac7eaf79Yorke Lee final String yearPattern = pattern.contains( 211c7ea6e965339cd7a6936f418d729ac92ac7eaf79Yorke Lee "de") ? "[^Mm]*[Yy]+[^Mm]*" : "[^DdMm]*[Yy]+[^DdMm]*"; 212c7ea6e965339cd7a6936f418d729ac92ac7eaf79Yorke Lee try { 213c7ea6e965339cd7a6936f418d729ac92ac7eaf79Yorke Lee // Eliminate the substring in pattern that matches the format for that of year 214c7ea6e965339cd7a6936f418d729ac92ac7eaf79Yorke Lee return new SimpleDateFormat(pattern.replaceAll(yearPattern, "")); 215c7ea6e965339cd7a6936f418d729ac92ac7eaf79Yorke Lee } catch (IllegalArgumentException e) { 216c7ea6e965339cd7a6936f418d729ac92ac7eaf79Yorke Lee return new SimpleDateFormat( 217c7ea6e965339cd7a6936f418d729ac92ac7eaf79Yorke Lee DateUtils.isMonthBeforeDay(context) ? "MMMM dd" : "dd MMMM"); 218c7ea6e965339cd7a6936f418d729ac92ac7eaf79Yorke Lee } 219c7ea6e965339cd7a6936f418d729ac92ac7eaf79Yorke Lee } 220ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee 221ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee /** 222ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee * Given a calendar (possibly containing only a day of the year), returns the earliest possible 223ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee * anniversary of the date that is equal to or after the current point in time if the date 224ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee * does not contain a year, or the date converted to the local time zone (if the date contains 225ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee * a year. 226ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee * 227ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee * @param target The date we wish to convert(in the UTC time zone). 228ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee * @return If date does not contain a year (year < 1900), returns the next earliest anniversary 229ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee * that is after the current point in time (in the local time zone). Otherwise, returns the 230ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee * adjusted Date in the local time zone. 231ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee */ 232ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee public static Date getNextAnnualDate(Calendar target) { 233ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee final Calendar today = Calendar.getInstance(); 234ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee today.setTime(new Date()); 235ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee 236ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee // Round the current time to the exact start of today so that when we compare 237ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee // today against the target date, both dates are set to exactly 0000H. 238ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee today.set(Calendar.HOUR_OF_DAY, 0); 239ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee today.set(Calendar.MINUTE, 0); 240ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee today.set(Calendar.SECOND, 0); 241ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee today.set(Calendar.MILLISECOND, 0); 242ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee 243ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee final boolean isYearSet = isYearSet(target); 244ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee final int targetYear = target.get(Calendar.YEAR); 245ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee final int targetMonth = target.get(Calendar.MONTH); 246ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee final int targetDay = target.get(Calendar.DAY_OF_MONTH); 247ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee final boolean isFeb29 = (targetMonth == Calendar.FEBRUARY && targetDay == 29); 248ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee final GregorianCalendar anniversary = new GregorianCalendar(); 249ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee // Convert from the UTC date to the local date. Set the year to today's year if the 250ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee // there is no provided year (targetYear < 1900) 251ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee anniversary.set(!isYearSet ? today.get(Calendar.YEAR) : targetYear, 252ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee targetMonth, targetDay); 253ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee // If the anniversary's date is before the start of today and there is no year set, 254ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee // increment the year by 1 so that the returned date is always equal to or greater than 255ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee // today. If the day is a leap year, keep going until we get the next leap year anniversary 256ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee // Otherwise if there is already a year set, simply return the exact date. 257ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee if (!isYearSet) { 258ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee int anniversaryYear = today.get(Calendar.YEAR); 259ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee if (anniversary.before(today) || 260ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee (isFeb29 && !anniversary.isLeapYear(anniversaryYear))) { 261ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee // If the target date is not Feb 29, then set the anniversary to the next year. 262ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee // Otherwise, keep going until we find the next leap year (this is not guaranteed 263ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee // to be in 4 years time). 264ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee do { 265ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee anniversaryYear +=1; 266ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee } while (isFeb29 && !anniversary.isLeapYear(anniversaryYear)); 267ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee anniversary.set(anniversaryYear, targetMonth, targetDay); 268ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee } 269ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee } 270ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee return anniversary.getTime(); 271ec6bfa0ce4a94edbafb97c7ae2b573abf2aa4a85Yorke Lee } 27245b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov} 273