DateIntervalFormat.java revision e8f98b22cbd859c5c2cad9866335128a95adcd81
1/*
2 * Copyright (C) 2013 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 libcore.icu;
18
19import java.util.Calendar;
20import java.util.Locale;
21import java.util.TimeZone;
22
23/**
24 * Exposes icu4c's DateIntervalFormat.
25 */
26public final class DateIntervalFormat {
27
28  // These are all public API in DateUtils. There are others, but they're either for use with
29  // other methods (like FORMAT_ABBREV_RELATIVE), don't internationalize (like FORMAT_CAP_AMPM),
30  // or have never been implemented anyway (like FORMAT_NO_YEAR).
31  public static final int FORMAT_SHOW_TIME = 0x00001;
32  public static final int FORMAT_SHOW_WEEKDAY = 0x00002;
33  public static final int FORMAT_SHOW_YEAR = 0x00004;
34  public static final int FORMAT_SHOW_DATE = 0x00010;
35  public static final int FORMAT_NO_MONTH_DAY = 0x00020;
36  public static final int FORMAT_12HOUR = 0x00040;
37  public static final int FORMAT_24HOUR = 0x00080;
38  public static final int FORMAT_UTC = 0x02000;
39  public static final int FORMAT_ABBREV_TIME = 0x04000;
40  public static final int FORMAT_ABBREV_WEEKDAY = 0x08000;
41  public static final int FORMAT_ABBREV_MONTH = 0x10000;
42  public static final int FORMAT_NUMERIC_DATE = 0x20000;
43  public static final int FORMAT_ABBREV_ALL = 0x80000;
44
45  // TODO: check whether icu4c's DateIntervalFormat is expensive enough to warrant a native peer.
46  private DateIntervalFormat() {
47  }
48
49  // This is public DateUtils API in frameworks/base.
50  public static String formatDateRange(long startMs, long endMs, int flags, String olsonId) {
51    if ((flags & FORMAT_UTC) != 0) {
52      olsonId = "UTC";
53    }
54    TimeZone tz = (olsonId != null) ? TimeZone.getTimeZone(olsonId) : TimeZone.getDefault();
55    return formatDateRange(Locale.getDefault(), tz, startMs, endMs, flags);
56  }
57
58  // This is our slightly more sensible internal API. (A truly sane replacement would take a
59  // skeleton instead of int flags.)
60  public static String formatDateRange(Locale locale, TimeZone tz, long startMs, long endMs, int flags) {
61    String skeleton = toSkeleton(tz, startMs, endMs, flags);
62    return formatDateInterval(skeleton, locale.toString(), tz.getID(), startMs, endMs);
63  }
64
65  private static String toSkeleton(TimeZone tz, long startMs, long endMs, int flags) {
66    Calendar startCalendar = Calendar.getInstance(tz);
67    startCalendar.setTimeInMillis(startMs);
68
69    Calendar endCalendar;
70    if (startMs == endMs) {
71      endCalendar = startCalendar;
72    } else {
73      endCalendar = Calendar.getInstance(tz);
74      endCalendar.setTimeInMillis(endMs);
75    }
76
77    if ((flags & FORMAT_ABBREV_ALL) != 0) {
78      flags |= FORMAT_ABBREV_MONTH | FORMAT_ABBREV_TIME | FORMAT_ABBREV_WEEKDAY;
79    }
80
81    String monthPart = "MMMM";
82    if ((flags & FORMAT_NUMERIC_DATE) != 0) {
83      monthPart = "M";
84    } else if ((flags & FORMAT_ABBREV_MONTH) != 0) {
85      monthPart = "MMM";
86    }
87
88    String weekPart = "EEEE";
89    if ((flags & FORMAT_ABBREV_WEEKDAY) != 0) {
90      weekPart = "EEE";
91    }
92
93    String timePart = "j"; // "j" means choose 12 or 24 hour based on current locale.
94    if ((flags & FORMAT_24HOUR) != 0) {
95      timePart = "H";
96    } else if ((flags & FORMAT_12HOUR) != 0) {
97      timePart = "h";
98    }
99
100    // If we've not been asked to abbreviate times, or we're using the 24-hour clock (where it
101    // never makes sense to leave out the minutes), include minutes. This gets us times like
102    // "4 PM" while avoiding times like "16" (for "16:00").
103    if ((flags & FORMAT_ABBREV_TIME) == 0 || (flags & FORMAT_24HOUR) != 0) {
104      timePart += "m";
105    } else {
106      // Otherwise, we're abbreviating a 12-hour time, and should only show the minutes
107      // if they're not both "00".
108      if (!(onTheHour(startCalendar) && onTheHour(endCalendar))) {
109        timePart = timePart + "m";
110      }
111    }
112
113    if (fallOnDifferentDates(startCalendar, endCalendar)) {
114      flags |= FORMAT_SHOW_DATE;
115    }
116
117    if (fallInSameMonth(startCalendar, endCalendar) && (flags & FORMAT_NO_MONTH_DAY) != 0) {
118      flags &= (~FORMAT_SHOW_WEEKDAY);
119      flags &= (~FORMAT_SHOW_TIME);
120    }
121
122    if ((flags & (FORMAT_SHOW_DATE | FORMAT_SHOW_TIME | FORMAT_SHOW_WEEKDAY)) == 0) {
123      flags |= FORMAT_SHOW_DATE;
124    }
125
126    StringBuilder builder = new StringBuilder();
127    if ((flags & (FORMAT_SHOW_DATE | FORMAT_NO_MONTH_DAY)) != 0) {
128      if ((flags & FORMAT_SHOW_YEAR) != 0) {
129        builder.append("y");
130      }
131      builder.append(monthPart);
132      if ((flags & FORMAT_NO_MONTH_DAY) == 0) {
133        builder.append("d");
134      }
135    }
136    if ((flags & FORMAT_SHOW_WEEKDAY) != 0) {
137      builder.append(weekPart);
138    }
139    if ((flags & FORMAT_SHOW_TIME) != 0) {
140      builder.append(timePart);
141    }
142    return builder.toString();
143  }
144
145  private static boolean onTheHour(Calendar c) {
146    return c.get(Calendar.MINUTE) == 0 && c.get(Calendar.SECOND) == 0;
147  }
148
149  private static boolean fallOnDifferentDates(Calendar c1, Calendar c2) {
150    return c1.get(Calendar.YEAR) != c2.get(Calendar.YEAR) ||
151        c1.get(Calendar.MONTH) != c2.get(Calendar.MONTH) ||
152        c1.get(Calendar.DAY_OF_MONTH) != c2.get(Calendar.DAY_OF_MONTH);
153  }
154
155  private static boolean fallInSameMonth(Calendar c1, Calendar c2) {
156    return c1.get(Calendar.MONTH) == c2.get(Calendar.MONTH);
157  }
158
159  private static native String formatDateInterval(String skeleton, String localeName, String timeZoneName, long fromDate, long toDate);
160}
161