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