1/*
2 * Copyright (C) 2012 Google Inc.
3 * Licensed to The Android Open Source Project.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 *      http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17package com.android.mail;
18
19import android.content.Context;
20import android.text.format.DateUtils;
21
22import java.util.Calendar;
23import java.util.Formatter;
24
25/**
26 * Convenience class to efficiently make multiple short date strings. Instantiating and reusing
27 * one of these builders is faster than repeatedly bringing up all the locale stuff.
28 *
29 */
30public class FormattedDateBuilder {
31
32    private final StringBuilder sb;
33    private final Formatter dateFormatter;
34    private final Context mContext;
35
36    public FormattedDateBuilder(Context context) {
37        mContext = context;
38        sb = new StringBuilder();
39        dateFormatter = new Formatter(sb);
40    }
41
42    /**
43     * This is used in the conversation list, and headers of collapsed messages in
44     * threaded conversations.
45     * Times on today's date will just display time, e.g. 8:15 AM
46     * Times not today, but within the same calendar year will display absolute date, e.g. Nov 6
47     * Times not in the same year display a numeric absolute date, e.g. 11/18/12
48     *
49     * @param when The time to generate a formatted date for
50     * @return The formatted date
51     */
52    public CharSequence formatShortDateTime(long when) {
53        if (DateUtils.isToday(when)) {
54            return formatDateTime(when, DateUtils.FORMAT_SHOW_TIME);
55        } else if (isCurrentYear(when)) {
56            return formatDateTime(when, DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_ABBREV_MONTH);
57        } else {
58            return formatDateTime(when, DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NUMERIC_DATE);
59        }
60    }
61
62    /**
63     * This is used in regular message headers.
64     * Times on today's date will just display time, e.g. 8:15 AM
65     * Times not today, but within two weeks ago will display relative date and time,
66     * e.g. 6 days ago, 8:15 AM
67     * Times more than two weeks ago but within the same calendar year will display
68     * absolute date and time, e.g. Nov 6, 8:15 AM
69     * Times not in the same year display a numeric absolute date, e.g. 11/18/12
70     *
71     * @param when The time to generate a formatted date for
72     * @return The formatted date
73     */
74    public CharSequence formatLongDateTime(long when) {
75        if (DateUtils.isToday(when)) {
76            return formatDateTime(when, DateUtils.FORMAT_SHOW_TIME);
77        } else if (isCurrentYear(when)) {
78            return getRelativeDateTimeString(mContext, when, DateUtils.DAY_IN_MILLIS,
79                    2 * DateUtils.WEEK_IN_MILLIS,
80                    DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_ABBREV_MONTH);
81        } else {
82            return formatDateTime(when, DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NUMERIC_DATE);
83        }
84    }
85
86    /**
87     * This is used in expanded details headers.
88     * Displays full date and time e.g. Tue, Nov 18, 2012, 8:15 AM, or
89     * Yesterday, Nov 18, 2012, 8:15 AM
90     *
91     * @param when The time to generate a formatted date for
92     * @return The formatted date
93     */
94    public CharSequence formatFullDateTime(long when) {
95        sb.setLength(0);
96        DateUtils.formatDateRange(mContext, dateFormatter, when, when,
97                DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_SHOW_DATE |
98                DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_ABBREV_ALL);
99        return sb.toString();
100    }
101
102    /**
103     * This is used for displaying dates when printing.
104     * Displays the full date, e.g. Tue, Nov 18, 2012 at 8:15 PM
105     *
106     * @param when The time to generate a formatted date for
107     * @return The formatted date
108     */
109    public String formatDateTimeForPrinting(long when) {
110        return mContext.getString(R.string.date_message_received_print,
111                formatDateTime(when, DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_WEEKDAY |
112                        DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_ABBREV_ALL),
113                        formatDateTime(when, DateUtils.FORMAT_SHOW_TIME));
114    }
115
116    private boolean isCurrentYear(long when) {
117        final Calendar nowCal = Calendar.getInstance();
118        final Calendar whenCal = Calendar.getInstance();
119        whenCal.setTimeInMillis(when);
120        return (nowCal.get(Calendar.YEAR) == whenCal.get(Calendar.YEAR));
121    }
122
123    private CharSequence formatDateTime(long when, int flags) {
124        sb.setLength(0);
125        DateUtils.formatDateRange(mContext, dateFormatter, when, when, flags);
126        return sb.toString();
127    }
128
129    /**
130     * A port of
131     * {@link DateUtils#getRelativeDateTimeString(android.content.Context, long, long, long, int)}
132     * that does not include the time in strings like "2 days ago".
133     */
134    private static CharSequence getRelativeDateTimeString(Context c, long time, long minResolution,
135            long transitionResolution, int flags) {
136        final long now = System.currentTimeMillis();
137        final long duration = Math.abs(now - time);
138
139        // getRelativeTimeSpanString() doesn't correctly format relative dates
140        // above a week or exact dates below a day, so clamp
141        // transitionResolution as needed.
142        if (transitionResolution > DateUtils.WEEK_IN_MILLIS) {
143            transitionResolution = DateUtils.WEEK_IN_MILLIS;
144        } else if (transitionResolution < DateUtils.DAY_IN_MILLIS) {
145            transitionResolution = DateUtils.DAY_IN_MILLIS;
146        }
147
148        if (duration < transitionResolution) {
149            return DateUtils.getRelativeTimeSpanString(time, now, minResolution, flags);
150        } else {
151            return DateUtils.getRelativeTimeSpanString(c, time, false);
152        }
153    }
154}
155