1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.contacts.util;
18
19import android.content.Context;
20import android.text.format.DateFormat;
21
22import java.text.ParsePosition;
23import java.text.SimpleDateFormat;
24import java.util.Calendar;
25import java.util.Date;
26import java.util.Locale;
27import java.util.TimeZone;
28
29/**
30 * Utility methods for processing dates.
31 */
32public class DateUtils {
33    public static final TimeZone UTC_TIMEZONE = TimeZone.getTimeZone("UTC");
34
35    // All the SimpleDateFormats in this class use the UTC timezone
36    public static final SimpleDateFormat NO_YEAR_DATE_FORMAT =
37            new SimpleDateFormat("--MM-dd", Locale.US);
38    /**
39     * When parsing a date without a year, the system assumes 1970, which wasn't a leap-year.
40     * Let's add a one-off hack for that day of the year
41     */
42    public static final String NO_YEAR_DATE_FEB29TH = "--02-29";
43    public static final SimpleDateFormat FULL_DATE_FORMAT =
44            new SimpleDateFormat("yyyy-MM-dd", Locale.US);
45    public static final SimpleDateFormat DATE_AND_TIME_FORMAT =
46            new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US);
47    public static final SimpleDateFormat NO_YEAR_DATE_AND_TIME_FORMAT =
48            new SimpleDateFormat("--MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US);
49
50    // Variations of ISO 8601 date format.  Do not change the order - it does affect the
51    // result in ambiguous cases.
52    private static final SimpleDateFormat[] DATE_FORMATS = {
53        FULL_DATE_FORMAT,
54        DATE_AND_TIME_FORMAT,
55        new SimpleDateFormat("yyyy-MM-dd'T'HH:mm'Z'", Locale.US),
56        new SimpleDateFormat("yyyyMMdd", Locale.US),
57        new SimpleDateFormat("yyyyMMdd'T'HHmmssSSS'Z'", Locale.US),
58        new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'", Locale.US),
59        new SimpleDateFormat("yyyyMMdd'T'HHmm'Z'", Locale.US),
60    };
61
62    private static final java.text.DateFormat FORMAT_WITHOUT_YEAR_MONTH_FIRST =
63            new SimpleDateFormat("MMMM dd");
64
65    private static final java.text.DateFormat FORMAT_WITHOUT_YEAR_DAY_FIRST =
66            new SimpleDateFormat("dd MMMM");
67
68    static {
69        for (SimpleDateFormat format : DATE_FORMATS) {
70            format.setLenient(true);
71            format.setTimeZone(UTC_TIMEZONE);
72        }
73        NO_YEAR_DATE_FORMAT.setTimeZone(UTC_TIMEZONE);
74        FORMAT_WITHOUT_YEAR_MONTH_FIRST.setTimeZone(UTC_TIMEZONE);
75        FORMAT_WITHOUT_YEAR_DAY_FIRST.setTimeZone(UTC_TIMEZONE);
76    }
77
78    /**
79     * Parses the supplied string to see if it looks like a date. If so,
80     * returns the date.  Otherwise, returns null.
81     */
82    public static Date parseDate(String string) {
83        ParsePosition parsePosition = new ParsePosition(0);
84        for (int i = 0; i < DATE_FORMATS.length; i++) {
85            SimpleDateFormat f = DATE_FORMATS[i];
86            synchronized (f) {
87                parsePosition.setIndex(0);
88                Date date = f.parse(string, parsePosition);
89                if (parsePosition.getIndex() == string.length()) {
90                    return date;
91                }
92            }
93        }
94        return null;
95    }
96
97    private static final Date getUtcDate(int year, int month, int dayOfMonth) {
98        final Calendar calendar = Calendar.getInstance(UTC_TIMEZONE, Locale.US);
99        calendar.set(Calendar.YEAR, year);
100        calendar.set(Calendar.MONTH, month);
101        calendar.set(Calendar.DAY_OF_MONTH, dayOfMonth);
102        return calendar.getTime();
103    }
104
105    /**
106     * Parses the supplied string to see if it looks like a date. If so,
107     * returns the same date in a cleaned-up format for the user.  Otherwise, returns
108     * the supplied string unchanged.
109     */
110    public static String formatDate(Context context, String string) {
111        if (string == null) {
112            return null;
113        }
114
115        string = string.trim();
116        if (string.length() == 0) {
117            return string;
118        }
119
120        ParsePosition parsePosition = new ParsePosition(0);
121
122        final boolean noYearParsed;
123        Date date;
124
125        // Unfortunately, we can't parse Feb 29th correctly, so let's handle this day seperately
126        if (NO_YEAR_DATE_FEB29TH.equals(string)) {
127            date = getUtcDate(0, Calendar.FEBRUARY, 29);
128            noYearParsed = true;
129        } else {
130            synchronized (NO_YEAR_DATE_FORMAT) {
131                date = NO_YEAR_DATE_FORMAT.parse(string, parsePosition);
132            }
133            noYearParsed = parsePosition.getIndex() == string.length();
134        }
135
136        if (noYearParsed) {
137            java.text.DateFormat outFormat = isMonthBeforeDay(context)
138                    ? FORMAT_WITHOUT_YEAR_MONTH_FIRST
139                    : FORMAT_WITHOUT_YEAR_DAY_FIRST;
140            synchronized (outFormat) {
141                return outFormat.format(date);
142            }
143        }
144
145        for (int i = 0; i < DATE_FORMATS.length; i++) {
146            SimpleDateFormat f = DATE_FORMATS[i];
147            synchronized (f) {
148                parsePosition.setIndex(0);
149                date = f.parse(string, parsePosition);
150                if (parsePosition.getIndex() == string.length()) {
151                    java.text.DateFormat outFormat = DateFormat.getDateFormat(context);
152                    outFormat.setTimeZone(UTC_TIMEZONE);
153                    return outFormat.format(date);
154                }
155            }
156        }
157        return string;
158    }
159
160    public static boolean isMonthBeforeDay(Context context) {
161        char[] dateFormatOrder = DateFormat.getDateFormatOrder(context);
162        for (int i = 0; i < dateFormatOrder.length; i++) {
163            if (dateFormatOrder[i] == DateFormat.DATE) {
164                return false;
165            }
166            if (dateFormatOrder[i] == DateFormat.MONTH) {
167                return true;
168            }
169        }
170        return false;
171    }
172}
173