1 /*
2  * Copyright (C) 2015 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 
17 package libcore.icu;
18 
19 import android.icu.impl.JavaTimeZone;
20 import android.icu.util.Calendar;
21 import android.icu.util.GregorianCalendar;
22 import android.icu.util.ULocale;
23 
24 /**
25  * Common methods and constants for the various ICU formatters used to support
26  * android.text.format.DateUtils.
27  */
28 public final class DateUtilsBridge {
29   // These are all public API in DateUtils. There are others, but they're either for use with
30   // other methods (like FORMAT_ABBREV_RELATIVE), don't internationalize (like FORMAT_CAP_AMPM),
31   // or have never been implemented anyway.
32   public static final int FORMAT_SHOW_TIME       = 0x00001;
33   public static final int FORMAT_SHOW_WEEKDAY    = 0x00002;
34   public static final int FORMAT_SHOW_YEAR       = 0x00004;
35   public static final int FORMAT_NO_YEAR         = 0x00008;
36   public static final int FORMAT_SHOW_DATE       = 0x00010;
37   public static final int FORMAT_NO_MONTH_DAY    = 0x00020;
38   public static final int FORMAT_12HOUR          = 0x00040;
39   public static final int FORMAT_24HOUR          = 0x00080;
40   public static final int FORMAT_UTC             = 0x02000;
41   public static final int FORMAT_ABBREV_TIME     = 0x04000;
42   public static final int FORMAT_ABBREV_WEEKDAY  = 0x08000;
43   public static final int FORMAT_ABBREV_MONTH    = 0x10000;
44   public static final int FORMAT_NUMERIC_DATE    = 0x20000;
45   public static final int FORMAT_ABBREV_RELATIVE = 0x40000;
46   public static final int FORMAT_ABBREV_ALL      = 0x80000;
47 
48   /**
49    * Creates an immutable ICU timezone backed by the specified libcore timezone data. At the time of
50    * writing the libcore implementation is faster but restricted to 1902 - 2038.
51    * Callers must not modify the {@code tz} after calling this method.
52    */
icuTimeZone(java.util.TimeZone tz)53   public static android.icu.util.TimeZone icuTimeZone(java.util.TimeZone tz) {
54     JavaTimeZone javaTimeZone = new JavaTimeZone(tz, null);
55     javaTimeZone.freeze(); // Optimization - allows the timezone to be copied cheaply.
56     return javaTimeZone;
57   }
58 
createIcuCalendar(android.icu.util.TimeZone icuTimeZone, ULocale icuLocale, long timeInMillis)59   public static Calendar createIcuCalendar(android.icu.util.TimeZone icuTimeZone, ULocale icuLocale,
60       long timeInMillis) {
61     Calendar calendar = new GregorianCalendar(icuTimeZone, icuLocale);
62     calendar.setTimeInMillis(timeInMillis);
63     return calendar;
64   }
65 
toSkeleton(Calendar calendar, int flags)66   public static String toSkeleton(Calendar calendar, int flags) {
67     return toSkeleton(calendar, calendar, flags);
68   }
69 
toSkeleton(Calendar startCalendar, Calendar endCalendar, int flags)70   public static String toSkeleton(Calendar startCalendar, Calendar endCalendar, int flags) {
71     if ((flags & FORMAT_ABBREV_ALL) != 0) {
72       flags |= FORMAT_ABBREV_MONTH | FORMAT_ABBREV_TIME | FORMAT_ABBREV_WEEKDAY;
73     }
74 
75     String monthPart = "MMMM";
76     if ((flags & FORMAT_NUMERIC_DATE) != 0) {
77       monthPart = "M";
78     } else if ((flags & FORMAT_ABBREV_MONTH) != 0) {
79       monthPart = "MMM";
80     }
81 
82     String weekPart = "EEEE";
83     if ((flags & FORMAT_ABBREV_WEEKDAY) != 0) {
84       weekPart = "EEE";
85     }
86 
87     String timePart = "j"; // "j" means choose 12 or 24 hour based on current locale.
88     if ((flags & FORMAT_24HOUR) != 0) {
89       timePart = "H";
90     } else if ((flags & FORMAT_12HOUR) != 0) {
91       timePart = "h";
92     }
93 
94     // If we've not been asked to abbreviate times, or we're using the 24-hour clock (where it
95     // never makes sense to leave out the minutes), include minutes. This gets us times like
96     // "4 PM" while avoiding times like "16" (for "16:00").
97     if ((flags & FORMAT_ABBREV_TIME) == 0 || (flags & FORMAT_24HOUR) != 0) {
98       timePart += "m";
99     } else {
100       // Otherwise, we're abbreviating a 12-hour time, and should only show the minutes
101       // if they're not both "00".
102       if (!(onTheHour(startCalendar) && onTheHour(endCalendar))) {
103         timePart = timePart + "m";
104       }
105     }
106 
107     if (fallOnDifferentDates(startCalendar, endCalendar)) {
108       flags |= FORMAT_SHOW_DATE;
109     }
110 
111     if (fallInSameMonth(startCalendar, endCalendar) && (flags & FORMAT_NO_MONTH_DAY) != 0) {
112       flags &= (~FORMAT_SHOW_WEEKDAY);
113       flags &= (~FORMAT_SHOW_TIME);
114     }
115 
116     if ((flags & (FORMAT_SHOW_DATE | FORMAT_SHOW_TIME | FORMAT_SHOW_WEEKDAY)) == 0) {
117       flags |= FORMAT_SHOW_DATE;
118     }
119 
120     // If we've been asked to show the date, work out whether we think we should show the year.
121     if ((flags & FORMAT_SHOW_DATE) != 0) {
122       if ((flags & FORMAT_SHOW_YEAR) != 0) {
123         // The caller explicitly wants us to show the year.
124       } else if ((flags & FORMAT_NO_YEAR) != 0) {
125         // The caller explicitly doesn't want us to show the year, even if we otherwise would.
126       } else if (!fallInSameYear(startCalendar, endCalendar) || !isThisYear(startCalendar)) {
127         flags |= FORMAT_SHOW_YEAR;
128       }
129     }
130 
131     StringBuilder builder = new StringBuilder();
132     if ((flags & (FORMAT_SHOW_DATE | FORMAT_NO_MONTH_DAY)) != 0) {
133       if ((flags & FORMAT_SHOW_YEAR) != 0) {
134         builder.append("y");
135       }
136       builder.append(monthPart);
137       if ((flags & FORMAT_NO_MONTH_DAY) == 0) {
138         builder.append("d");
139       }
140     }
141     if ((flags & FORMAT_SHOW_WEEKDAY) != 0) {
142       builder.append(weekPart);
143     }
144     if ((flags & FORMAT_SHOW_TIME) != 0) {
145       builder.append(timePart);
146     }
147     return builder.toString();
148   }
149 
dayDistance(Calendar c1, Calendar c2)150   public static int dayDistance(Calendar c1, Calendar c2) {
151     return c2.get(Calendar.JULIAN_DAY) - c1.get(Calendar.JULIAN_DAY);
152   }
153 
onTheHour(Calendar c)154   private static boolean onTheHour(Calendar c) {
155     return c.get(Calendar.MINUTE) == 0 && c.get(Calendar.SECOND) == 0;
156   }
157 
fallOnDifferentDates(Calendar c1, Calendar c2)158   private static boolean fallOnDifferentDates(Calendar c1, Calendar c2) {
159     return c1.get(Calendar.YEAR) != c2.get(Calendar.YEAR) ||
160         c1.get(Calendar.MONTH) != c2.get(Calendar.MONTH) ||
161         c1.get(Calendar.DAY_OF_MONTH) != c2.get(Calendar.DAY_OF_MONTH);
162   }
163 
fallInSameMonth(Calendar c1, Calendar c2)164   private static boolean fallInSameMonth(Calendar c1, Calendar c2) {
165     return c1.get(Calendar.MONTH) == c2.get(Calendar.MONTH);
166   }
167 
fallInSameYear(Calendar c1, Calendar c2)168   private static boolean fallInSameYear(Calendar c1, Calendar c2) {
169     return c1.get(Calendar.YEAR) == c2.get(Calendar.YEAR);
170   }
171 
isThisYear(Calendar c)172   private static boolean isThisYear(Calendar c) {
173     Calendar now = (Calendar) c.clone();
174     now.setTimeInMillis(System.currentTimeMillis());
175     return c.get(Calendar.YEAR) == now.get(Calendar.YEAR);
176   }
177 }
178