1 package com.android.contacts.interactions;
2 
3 import com.android.contacts.R;
4 
5 import android.content.Context;
6 import android.content.res.Resources;
7 import android.text.format.DateFormat;
8 import android.text.format.DateUtils;
9 import android.text.format.Time;
10 
11 import java.util.Formatter;
12 import java.util.Locale;
13 
14 /**
15  * The following methods were pulled from
16  * {@link com.android.calendar.EventInfoFragment.updateEvent(View view)}
17  * TODO: Move this to frameworks/opt
18  */
19 public class CalendarInteractionUtils {
20 
21     // Using int constants as a return value instead of an enum to minimize resources.
22     private static final int TODAY = 1;
23     private static final int TOMORROW = 2;
24     private static final int NONE = 0;
25 
26     /**
27      * Returns a string description of the specified time interval.
28      */
getDisplayedDatetime(long startMillis, long endMillis, long currentMillis, String localTimezone, boolean allDay, Context context)29     public static String getDisplayedDatetime(long startMillis, long endMillis, long currentMillis,
30             String localTimezone, boolean allDay, Context context) {
31         // Configure date/time formatting.
32         int flagsDate = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_WEEKDAY;
33         int flagsTime = DateUtils.FORMAT_SHOW_TIME;
34         if (DateFormat.is24HourFormat(context)) {
35             flagsTime |= DateUtils.FORMAT_24HOUR;
36         }
37 
38         Time currentTime = new Time(localTimezone);
39         currentTime.set(currentMillis);
40         Resources resources = context.getResources();
41         String datetimeString = null;
42         if (allDay) {
43             // All day events require special timezone adjustment.
44             long localStartMillis = convertAlldayUtcToLocal(null, startMillis, localTimezone);
45             long localEndMillis = convertAlldayUtcToLocal(null, endMillis, localTimezone);
46             if (singleDayEvent(localStartMillis, localEndMillis, currentTime.gmtoff)) {
47                 // If possible, use "Today" or "Tomorrow" instead of a full date string.
48                 int todayOrTomorrow = isTodayOrTomorrow(context.getResources(),
49                         localStartMillis, currentMillis, currentTime.gmtoff);
50                 if (TODAY == todayOrTomorrow) {
51                     datetimeString = resources.getString(R.string.today);
52                 } else if (TOMORROW == todayOrTomorrow) {
53                     datetimeString = resources.getString(R.string.tomorrow);
54                 }
55             }
56             if (datetimeString == null) {
57                 // For multi-day allday events or single-day all-day events that are not
58                 // today or tomorrow, use framework formatter.
59                 Formatter f = new Formatter(new StringBuilder(50), Locale.getDefault());
60                 datetimeString = DateUtils.formatDateRange(context, f, startMillis,
61                         endMillis, flagsDate, Time.TIMEZONE_UTC).toString();
62             }
63         } else {
64             if (singleDayEvent(startMillis, endMillis, currentTime.gmtoff)) {
65                 // Format the time.
66                 String timeString = formatDateRange(context, startMillis, endMillis,
67                         flagsTime);
68 
69                 // If possible, use "Today" or "Tomorrow" instead of a full date string.
70                 int todayOrTomorrow = isTodayOrTomorrow(context.getResources(), startMillis,
71                         currentMillis, currentTime.gmtoff);
72                 if (TODAY == todayOrTomorrow) {
73                     // Example: "Today at 1:00pm - 2:00 pm"
74                     datetimeString = resources.getString(R.string.today_at_time_fmt,
75                             timeString);
76                 } else if (TOMORROW == todayOrTomorrow) {
77                     // Example: "Tomorrow at 1:00pm - 2:00 pm"
78                     datetimeString = resources.getString(R.string.tomorrow_at_time_fmt,
79                             timeString);
80                 } else {
81                     // Format the full date. Example: "Thursday, April 12, 1:00pm - 2:00pm"
82                     String dateString = formatDateRange(context, startMillis, endMillis,
83                             flagsDate);
84                     datetimeString = resources.getString(R.string.date_time_fmt, dateString,
85                             timeString);
86                 }
87             } else {
88                 // For multiday events, shorten day/month names.
89                 // Example format: "Fri Apr 6, 5:00pm - Sun, Apr 8, 6:00pm"
90                 int flagsDatetime = flagsDate | flagsTime | DateUtils.FORMAT_ABBREV_MONTH |
91                         DateUtils.FORMAT_ABBREV_WEEKDAY;
92                 datetimeString = formatDateRange(context, startMillis, endMillis,
93                         flagsDatetime);
94             }
95         }
96         return datetimeString;
97     }
98 
99     /**
100      * Convert given UTC time into current local time. This assumes it is for an
101      * allday event and will adjust the time to be on a midnight boundary.
102      *
103      * @param recycle Time object to recycle, otherwise null.
104      * @param utcTime Time to convert, in UTC.
105      * @param tz The time zone to convert this time to.
106      */
convertAlldayUtcToLocal(Time recycle, long utcTime, String tz)107     private static long convertAlldayUtcToLocal(Time recycle, long utcTime, String tz) {
108         if (recycle == null) {
109             recycle = new Time();
110         }
111         recycle.timezone = Time.TIMEZONE_UTC;
112         recycle.set(utcTime);
113         recycle.timezone = tz;
114         return recycle.normalize(true);
115     }
116 
convertAlldayLocalToUTC(Time recycle, long localTime, String tz)117     public static long convertAlldayLocalToUTC(Time recycle, long localTime, String tz) {
118         if (recycle == null) {
119             recycle = new Time();
120         }
121         recycle.timezone = tz;
122         recycle.set(localTime);
123         recycle.timezone = Time.TIMEZONE_UTC;
124         return recycle.normalize(true);
125     }
126 
127     /**
128      * Returns whether the specified time interval is in a single day.
129      */
singleDayEvent(long startMillis, long endMillis, long localGmtOffset)130     private static boolean singleDayEvent(long startMillis, long endMillis, long localGmtOffset) {
131         if (startMillis == endMillis) {
132             return true;
133         }
134 
135         // An event ending at midnight should still be a single-day event, so check
136         // time end-1.
137         int startDay = Time.getJulianDay(startMillis, localGmtOffset);
138         int endDay = Time.getJulianDay(endMillis - 1, localGmtOffset);
139         return startDay == endDay;
140     }
141 
142     /**
143      * Returns TODAY or TOMORROW if applicable.  Otherwise returns NONE.
144      */
isTodayOrTomorrow(Resources r, long dayMillis, long currentMillis, long localGmtOffset)145     private static int isTodayOrTomorrow(Resources r, long dayMillis,
146             long currentMillis, long localGmtOffset) {
147         int startDay = Time.getJulianDay(dayMillis, localGmtOffset);
148         int currentDay = Time.getJulianDay(currentMillis, localGmtOffset);
149 
150         int days = startDay - currentDay;
151         if (days == 1) {
152             return TOMORROW;
153         } else if (days == 0) {
154             return TODAY;
155         } else {
156             return NONE;
157         }
158     }
159 
160     /**
161      * Formats a date or a time range according to the local conventions.
162      *
163      * This formats a date/time range using Calendar's time zone and the
164      * local conventions for the region of the device.
165      *
166      * If the {@link DateUtils#FORMAT_UTC} flag is used it will pass in
167      * the UTC time zone instead.
168      *
169      * @param context the context is required only if the time is shown
170      * @param startMillis the start time in UTC milliseconds
171      * @param endMillis the end time in UTC milliseconds
172      * @param flags a bit mask of options See
173      * {@link DateUtils#formatDateRange(Context, Formatter, long, long, int, String) formatDateRange}
174      * @return a string containing the formatted date/time range.
175      */
formatDateRange(Context context, long startMillis, long endMillis, int flags)176     private static String formatDateRange(Context context, long startMillis,
177             long endMillis, int flags) {
178         String date;
179         String tz;
180         if ((flags & DateUtils.FORMAT_UTC) != 0) {
181             tz = Time.TIMEZONE_UTC;
182         } else {
183             tz = Time.getCurrentTimezone();
184         }
185         StringBuilder sb = new StringBuilder(50);
186         Formatter f = new Formatter(sb, Locale.getDefault());
187         sb.setLength(0);
188         date = DateUtils.formatDateRange(context, f, startMillis, endMillis, flags,
189                 tz).toString();
190         return date;
191     }
192 }
193