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
2245b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikovimport java.text.ParsePosition;
2345b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikovimport java.text.SimpleDateFormat;
245a7a26962cc781d675280c790789e2dea28a7b34Daniel Lehmannimport java.util.Calendar;
2545b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikovimport java.util.Date;
264b648485346462dc26c3a469caeae8589f5fbd58Daniel Lehmannimport java.util.Locale;
274b648485346462dc26c3a469caeae8589f5fbd58Daniel Lehmannimport java.util.TimeZone;
2845b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov
2945b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov/**
3045b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov * Utility methods for processing dates.
3145b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov */
3245b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikovpublic class DateUtils {
334b648485346462dc26c3a469caeae8589f5fbd58Daniel Lehmann    public static final TimeZone UTC_TIMEZONE = TimeZone.getTimeZone("UTC");
344b648485346462dc26c3a469caeae8589f5fbd58Daniel Lehmann
354b648485346462dc26c3a469caeae8589f5fbd58Daniel Lehmann    // All the SimpleDateFormats in this class use the UTC timezone
364b648485346462dc26c3a469caeae8589f5fbd58Daniel Lehmann    public static final SimpleDateFormat NO_YEAR_DATE_FORMAT =
374b648485346462dc26c3a469caeae8589f5fbd58Daniel Lehmann            new SimpleDateFormat("--MM-dd", Locale.US);
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";
434b648485346462dc26c3a469caeae8589f5fbd58Daniel Lehmann    public static final SimpleDateFormat FULL_DATE_FORMAT =
444b648485346462dc26c3a469caeae8589f5fbd58Daniel Lehmann            new SimpleDateFormat("yyyy-MM-dd", Locale.US);
45392ccec3b56e8074a5a028af28106134b39f64bcDaniel Lehmann    public static final SimpleDateFormat DATE_AND_TIME_FORMAT =
464b648485346462dc26c3a469caeae8589f5fbd58Daniel Lehmann            new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US);
47dcf751d75ad5d31620a4216d8c53fd59525e8bd8Makoto Onuki    public static final SimpleDateFormat NO_YEAR_DATE_AND_TIME_FORMAT =
48dcf751d75ad5d31620a4216d8c53fd59525e8bd8Makoto Onuki            new SimpleDateFormat("--MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US);
4945b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov
502b3f95cc12b76523410782d4178562ce241410efDmitri Plotnikov    // Variations of ISO 8601 date format.  Do not change the order - it does affect the
512b3f95cc12b76523410782d4178562ce241410efDmitri Plotnikov    // result in ambiguous cases.
5245b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov    private static final SimpleDateFormat[] DATE_FORMATS = {
53392ccec3b56e8074a5a028af28106134b39f64bcDaniel Lehmann        FULL_DATE_FORMAT,
54392ccec3b56e8074a5a028af28106134b39f64bcDaniel Lehmann        DATE_AND_TIME_FORMAT,
554b648485346462dc26c3a469caeae8589f5fbd58Daniel Lehmann        new SimpleDateFormat("yyyy-MM-dd'T'HH:mm'Z'", Locale.US),
564b648485346462dc26c3a469caeae8589f5fbd58Daniel Lehmann        new SimpleDateFormat("yyyyMMdd", Locale.US),
574b648485346462dc26c3a469caeae8589f5fbd58Daniel Lehmann        new SimpleDateFormat("yyyyMMdd'T'HHmmssSSS'Z'", Locale.US),
584b648485346462dc26c3a469caeae8589f5fbd58Daniel Lehmann        new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'", Locale.US),
594b648485346462dc26c3a469caeae8589f5fbd58Daniel Lehmann        new SimpleDateFormat("yyyyMMdd'T'HHmm'Z'", Locale.US),
6045b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov    };
612b3f95cc12b76523410782d4178562ce241410efDmitri Plotnikov
6245b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov    private static final java.text.DateFormat FORMAT_WITHOUT_YEAR_MONTH_FIRST =
6345b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov            new SimpleDateFormat("MMMM dd");
6445b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov
6511812c59ee140d62eaf3a8d826d2018767d490c5Daniel Lehmann    private static final java.text.DateFormat FORMAT_WITHOUT_YEAR_DAY_FIRST =
6645b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov            new SimpleDateFormat("dd MMMM");
6745b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov
684b648485346462dc26c3a469caeae8589f5fbd58Daniel Lehmann    static {
694b648485346462dc26c3a469caeae8589f5fbd58Daniel Lehmann        for (SimpleDateFormat format : DATE_FORMATS) {
704b648485346462dc26c3a469caeae8589f5fbd58Daniel Lehmann            format.setLenient(true);
714b648485346462dc26c3a469caeae8589f5fbd58Daniel Lehmann            format.setTimeZone(UTC_TIMEZONE);
724b648485346462dc26c3a469caeae8589f5fbd58Daniel Lehmann        }
734b648485346462dc26c3a469caeae8589f5fbd58Daniel Lehmann        NO_YEAR_DATE_FORMAT.setTimeZone(UTC_TIMEZONE);
744b648485346462dc26c3a469caeae8589f5fbd58Daniel Lehmann        FORMAT_WITHOUT_YEAR_MONTH_FIRST.setTimeZone(UTC_TIMEZONE);
7511812c59ee140d62eaf3a8d826d2018767d490c5Daniel Lehmann        FORMAT_WITHOUT_YEAR_DAY_FIRST.setTimeZone(UTC_TIMEZONE);
764b648485346462dc26c3a469caeae8589f5fbd58Daniel Lehmann    }
774b648485346462dc26c3a469caeae8589f5fbd58Daniel Lehmann
7845b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov    /**
7945b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov     * Parses the supplied string to see if it looks like a date. If so,
802b3f95cc12b76523410782d4178562ce241410efDmitri Plotnikov     * returns the date.  Otherwise, returns null.
812b3f95cc12b76523410782d4178562ce241410efDmitri Plotnikov     */
822b3f95cc12b76523410782d4178562ce241410efDmitri Plotnikov    public static Date parseDate(String string) {
832b3f95cc12b76523410782d4178562ce241410efDmitri Plotnikov        ParsePosition parsePosition = new ParsePosition(0);
842b3f95cc12b76523410782d4178562ce241410efDmitri Plotnikov        for (int i = 0; i < DATE_FORMATS.length; i++) {
852b3f95cc12b76523410782d4178562ce241410efDmitri Plotnikov            SimpleDateFormat f = DATE_FORMATS[i];
862b3f95cc12b76523410782d4178562ce241410efDmitri Plotnikov            synchronized (f) {
872b3f95cc12b76523410782d4178562ce241410efDmitri Plotnikov                parsePosition.setIndex(0);
882b3f95cc12b76523410782d4178562ce241410efDmitri Plotnikov                Date date = f.parse(string, parsePosition);
892b3f95cc12b76523410782d4178562ce241410efDmitri Plotnikov                if (parsePosition.getIndex() == string.length()) {
902b3f95cc12b76523410782d4178562ce241410efDmitri Plotnikov                    return date;
912b3f95cc12b76523410782d4178562ce241410efDmitri Plotnikov                }
922b3f95cc12b76523410782d4178562ce241410efDmitri Plotnikov            }
932b3f95cc12b76523410782d4178562ce241410efDmitri Plotnikov        }
942b3f95cc12b76523410782d4178562ce241410efDmitri Plotnikov        return null;
952b3f95cc12b76523410782d4178562ce241410efDmitri Plotnikov    }
962b3f95cc12b76523410782d4178562ce241410efDmitri Plotnikov
975a7a26962cc781d675280c790789e2dea28a7b34Daniel Lehmann    private static final Date getUtcDate(int year, int month, int dayOfMonth) {
985a7a26962cc781d675280c790789e2dea28a7b34Daniel Lehmann        final Calendar calendar = Calendar.getInstance(UTC_TIMEZONE, Locale.US);
995a7a26962cc781d675280c790789e2dea28a7b34Daniel Lehmann        calendar.set(Calendar.YEAR, year);
1005a7a26962cc781d675280c790789e2dea28a7b34Daniel Lehmann        calendar.set(Calendar.MONTH, month);
1015a7a26962cc781d675280c790789e2dea28a7b34Daniel Lehmann        calendar.set(Calendar.DAY_OF_MONTH, dayOfMonth);
1025a7a26962cc781d675280c790789e2dea28a7b34Daniel Lehmann        return calendar.getTime();
1035a7a26962cc781d675280c790789e2dea28a7b34Daniel Lehmann    }
1045a7a26962cc781d675280c790789e2dea28a7b34Daniel Lehmann
1052b3f95cc12b76523410782d4178562ce241410efDmitri Plotnikov    /**
1062b3f95cc12b76523410782d4178562ce241410efDmitri Plotnikov     * Parses the supplied string to see if it looks like a date. If so,
1074b648485346462dc26c3a469caeae8589f5fbd58Daniel Lehmann     * returns the same date in a cleaned-up format for the user.  Otherwise, returns
10845b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov     * the supplied string unchanged.
10945b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov     */
11045b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov    public static String formatDate(Context context, String string) {
11145b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov        if (string == null) {
11245b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov            return null;
11345b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov        }
11445b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov
11545b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov        string = string.trim();
11645b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov        if (string.length() == 0) {
11745b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov            return string;
11845b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov        }
11945b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov
12045b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov        ParsePosition parsePosition = new ParsePosition(0);
12145b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov
1225a7a26962cc781d675280c790789e2dea28a7b34Daniel Lehmann        final boolean noYearParsed;
1232b3f95cc12b76523410782d4178562ce241410efDmitri Plotnikov        Date date;
1242b3f95cc12b76523410782d4178562ce241410efDmitri Plotnikov
1255a7a26962cc781d675280c790789e2dea28a7b34Daniel Lehmann        // Unfortunately, we can't parse Feb 29th correctly, so let's handle this day seperately
1265a7a26962cc781d675280c790789e2dea28a7b34Daniel Lehmann        if (NO_YEAR_DATE_FEB29TH.equals(string)) {
1275a7a26962cc781d675280c790789e2dea28a7b34Daniel Lehmann            date = getUtcDate(0, Calendar.FEBRUARY, 29);
1285a7a26962cc781d675280c790789e2dea28a7b34Daniel Lehmann            noYearParsed = true;
1295a7a26962cc781d675280c790789e2dea28a7b34Daniel Lehmann        } else {
1305a7a26962cc781d675280c790789e2dea28a7b34Daniel Lehmann            synchronized (NO_YEAR_DATE_FORMAT) {
1315a7a26962cc781d675280c790789e2dea28a7b34Daniel Lehmann                date = NO_YEAR_DATE_FORMAT.parse(string, parsePosition);
1325a7a26962cc781d675280c790789e2dea28a7b34Daniel Lehmann            }
1335a7a26962cc781d675280c790789e2dea28a7b34Daniel Lehmann            noYearParsed = parsePosition.getIndex() == string.length();
1342b3f95cc12b76523410782d4178562ce241410efDmitri Plotnikov        }
1352b3f95cc12b76523410782d4178562ce241410efDmitri Plotnikov
1365a7a26962cc781d675280c790789e2dea28a7b34Daniel Lehmann        if (noYearParsed) {
13711812c59ee140d62eaf3a8d826d2018767d490c5Daniel Lehmann            java.text.DateFormat outFormat = isMonthBeforeDay(context)
1382b3f95cc12b76523410782d4178562ce241410efDmitri Plotnikov                    ? FORMAT_WITHOUT_YEAR_MONTH_FIRST
13911812c59ee140d62eaf3a8d826d2018767d490c5Daniel Lehmann                    : FORMAT_WITHOUT_YEAR_DAY_FIRST;
1402b3f95cc12b76523410782d4178562ce241410efDmitri Plotnikov            synchronized (outFormat) {
1412b3f95cc12b76523410782d4178562ce241410efDmitri Plotnikov                return outFormat.format(date);
1422b3f95cc12b76523410782d4178562ce241410efDmitri Plotnikov            }
1432b3f95cc12b76523410782d4178562ce241410efDmitri Plotnikov        }
1442b3f95cc12b76523410782d4178562ce241410efDmitri Plotnikov
14545b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov        for (int i = 0; i < DATE_FORMATS.length; i++) {
14645b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov            SimpleDateFormat f = DATE_FORMATS[i];
14745b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov            synchronized (f) {
14845b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov                parsePosition.setIndex(0);
1492b3f95cc12b76523410782d4178562ce241410efDmitri Plotnikov                date = f.parse(string, parsePosition);
15045b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov                if (parsePosition.getIndex() == string.length()) {
1512b3f95cc12b76523410782d4178562ce241410efDmitri Plotnikov                    java.text.DateFormat outFormat = DateFormat.getDateFormat(context);
1524b648485346462dc26c3a469caeae8589f5fbd58Daniel Lehmann                    outFormat.setTimeZone(UTC_TIMEZONE);
1534b648485346462dc26c3a469caeae8589f5fbd58Daniel Lehmann                    return outFormat.format(date);
15445b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov                }
15545b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov            }
15645b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov        }
15745b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov        return string;
15845b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov    }
15945b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov
16011812c59ee140d62eaf3a8d826d2018767d490c5Daniel Lehmann    public static boolean isMonthBeforeDay(Context context) {
16145b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov        char[] dateFormatOrder = DateFormat.getDateFormatOrder(context);
16245b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov        for (int i = 0; i < dateFormatOrder.length; i++) {
16345b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov            if (dateFormatOrder[i] == DateFormat.DATE) {
16445b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov                return false;
16545b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov            }
16645b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov            if (dateFormatOrder[i] == DateFormat.MONTH) {
16745b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov                return true;
16845b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov            }
16945b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov        }
17045b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov        return false;
17145b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov    }
17245b4402f0c05b21312dd8d647ae549d43057537dDmitri Plotnikov}
173