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