blob: de2dcce5bc5dfb3d906b2b5c5952000eb535157a [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
2 * Copyright (C) 2006 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 android.text.format;
18
Joachim Sauer20c5ef72017-06-01 11:22:26 +010019import android.annotation.NonNull;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080020import android.content.Context;
Selim Cinek9c4a7072014-11-21 17:44:34 +010021import android.os.UserHandle;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080022import android.provider.Settings;
23import android.text.SpannableStringBuilder;
24import android.text.Spanned;
25import android.text.SpannedString;
26
Aurimas Liutikas4037d512016-10-11 17:20:06 -070027import libcore.icu.ICU;
28import libcore.icu.LocaleData;
29
30import java.text.SimpleDateFormat;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080031import java.util.Calendar;
32import java.util.Date;
33import java.util.GregorianCalendar;
34import java.util.Locale;
35import java.util.TimeZone;
Elliott Hughes4caba612013-01-14 15:48:27 -080036
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080037/**
Elliott Hughes8326b9a2013-03-08 15:06:14 -080038 * Utility class for producing strings with formatted date/time.
39 *
40 * <p>Most callers should avoid supplying their own format strings to this
41 * class' {@code format} methods and rely on the correctly localized ones
42 * supplied by the system. This class' factory methods return
43 * appropriately-localized {@link java.text.DateFormat} instances, suitable
44 * for both formatting and parsing dates. For the canonical documentation
45 * of format strings, see {@link java.text.SimpleDateFormat}.
46 *
Elliott Hughes031b5812013-04-02 11:56:23 -070047 * <p>In cases where the system does not provide a suitable pattern,
48 * this class offers the {@link #getBestDateTimePattern} method.
49 *
Elliott Hughesfc55c2b2013-03-18 14:59:59 -070050 * <p>The {@code format} methods in this class implement a subset of Unicode
Elliott Hughes8326b9a2013-03-08 15:06:14 -080051 * <a href="http://www.unicode.org/reports/tr35/#Date_Format_Patterns">UTS #35</a> patterns.
Elliott Hughesfc55c2b2013-03-18 14:59:59 -070052 * The subset currently supported by this class includes the following format characters:
53 * {@code acdEHhLKkLMmsyz}. Up to API level 17, only {@code adEhkMmszy} were supported.
54 * Note that this class incorrectly implements {@code k} as if it were {@code H} for backwards
55 * compatibility.
56 *
57 * <p>See {@link java.text.SimpleDateFormat} for more documentation
58 * about patterns, or if you need a more complete or correct implementation.
59 * Note that the non-{@code format} methods in this class are implemented by
60 * {@code SimpleDateFormat}.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080061 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080062public class DateFormat {
Narayan Kamath9d68b3c2014-10-22 12:55:58 +010063 /**
64 * @deprecated Use a literal {@code '} instead.
65 * @removed
66 */
Elliott Hughes8326b9a2013-03-08 15:06:14 -080067 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080068 public static final char QUOTE = '\'';
Elliott Hughes8326b9a2013-03-08 15:06:14 -080069
Narayan Kamath9d68b3c2014-10-22 12:55:58 +010070 /**
71 * @deprecated Use a literal {@code 'a'} instead.
72 * @removed
73 */
Elliott Hughes8326b9a2013-03-08 15:06:14 -080074 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080075 public static final char AM_PM = 'a';
76
Narayan Kamath9d68b3c2014-10-22 12:55:58 +010077 /**
78 * @deprecated Use a literal {@code 'a'} instead; 'A' was always equivalent to 'a'.
79 * @removed
80 */
Elliott Hughes8326b9a2013-03-08 15:06:14 -080081 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080082 public static final char CAPITAL_AM_PM = 'A';
83
Narayan Kamath9d68b3c2014-10-22 12:55:58 +010084 /**
85 * @deprecated Use a literal {@code 'd'} instead.
86 * @removed
87 */
Elliott Hughes8326b9a2013-03-08 15:06:14 -080088 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080089 public static final char DATE = 'd';
90
Narayan Kamath9d68b3c2014-10-22 12:55:58 +010091 /**
92 * @deprecated Use a literal {@code 'E'} instead.
93 * @removed
94 */
Elliott Hughes8326b9a2013-03-08 15:06:14 -080095 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080096 public static final char DAY = 'E';
97
Narayan Kamath9d68b3c2014-10-22 12:55:58 +010098 /**
99 * @deprecated Use a literal {@code 'h'} instead.
100 * @removed
101 */
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800102 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800103 public static final char HOUR = 'h';
104
Elliott Hughesfc55c2b2013-03-18 14:59:59 -0700105 /**
106 * @deprecated Use a literal {@code 'H'} (for compatibility with {@link SimpleDateFormat}
107 * and Unicode) or {@code 'k'} (for compatibility with Android releases up to and including
108 * Jelly Bean MR-1) instead. Note that the two are incompatible.
Narayan Kamath9d68b3c2014-10-22 12:55:58 +0100109 *
110 * @removed
Elliott Hughesfc55c2b2013-03-18 14:59:59 -0700111 */
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800112 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800113 public static final char HOUR_OF_DAY = 'k';
114
Narayan Kamath9d68b3c2014-10-22 12:55:58 +0100115 /**
116 * @deprecated Use a literal {@code 'm'} instead.
117 * @removed
118 */
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800119 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800120 public static final char MINUTE = 'm';
121
Narayan Kamath9d68b3c2014-10-22 12:55:58 +0100122 /**
123 * @deprecated Use a literal {@code 'M'} instead.
124 * @removed
125 */
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800126 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800127 public static final char MONTH = 'M';
128
Narayan Kamath9d68b3c2014-10-22 12:55:58 +0100129 /**
130 * @deprecated Use a literal {@code 'L'} instead.
131 * @removed
132 */
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800133 @Deprecated
Elliott Hughes34de3bc2012-09-14 21:10:05 -0700134 public static final char STANDALONE_MONTH = 'L';
135
Narayan Kamath9d68b3c2014-10-22 12:55:58 +0100136 /**
137 * @deprecated Use a literal {@code 's'} instead.
138 * @removed
139 */
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800140 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800141 public static final char SECONDS = 's';
142
Narayan Kamath9d68b3c2014-10-22 12:55:58 +0100143 /**
144 * @deprecated Use a literal {@code 'z'} instead.
145 * @removed
146 */
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800147 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800148 public static final char TIME_ZONE = 'z';
149
Narayan Kamath9d68b3c2014-10-22 12:55:58 +0100150 /**
151 * @deprecated Use a literal {@code 'y'} instead.
152 * @removed
153 */
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800154 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800155 public static final char YEAR = 'y';
156
157
158 private static final Object sLocaleLock = new Object();
159 private static Locale sIs24HourLocale;
160 private static boolean sIs24Hour;
161
162
163 /**
164 * Returns true if user preference is set to 24-hour format.
165 * @param context the context to use for the content resolver
166 * @return true if 24 hour time format is selected, false otherwise.
167 */
168 public static boolean is24HourFormat(Context context) {
Selim Cinek9c4a7072014-11-21 17:44:34 +0100169 return is24HourFormat(context, UserHandle.myUserId());
170 }
171
172 /**
173 * Returns true if user preference with the given user handle is set to 24-hour format.
174 * @param context the context to use for the content resolver
175 * @param userHandle the user handle of the user to query.
176 * @return true if 24 hour time format is selected, false otherwise.
177 *
178 * @hide
179 */
180 public static boolean is24HourFormat(Context context, int userHandle) {
Roozbeh Pournader31504342017-07-20 12:34:06 -0700181 final String value = Settings.System.getStringForUser(context.getContentResolver(),
Selim Cinek9c4a7072014-11-21 17:44:34 +0100182 Settings.System.TIME_12_24, userHandle);
Roozbeh Pournader31504342017-07-20 12:34:06 -0700183 if (value != null) {
184 return value.equals("24");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800185 }
186
Joachim Sauer20c5ef72017-06-01 11:22:26 +0100187 return is24HourLocale(context.getResources().getConfiguration().locale);
188 }
Roozbeh Pournader31504342017-07-20 12:34:06 -0700189
Joachim Sauer20c5ef72017-06-01 11:22:26 +0100190 /**
191 * Returns true if the specified locale uses a 24-hour time format by default, ignoring user
192 * settings.
193 * @param locale the locale to check
194 * @return true if the locale uses a 24 hour time format by default, false otherwise
195 * @hide
196 */
197 public static boolean is24HourLocale(@NonNull Locale locale) {
Roozbeh Pournader31504342017-07-20 12:34:06 -0700198 synchronized (sLocaleLock) {
199 if (sIs24HourLocale != null && sIs24HourLocale.equals(locale)) {
200 return sIs24Hour;
201 }
202 }
203
204 final java.text.DateFormat natural =
205 java.text.DateFormat.getTimeInstance(java.text.DateFormat.LONG, locale);
206
207 final boolean is24Hour;
208 if (natural instanceof SimpleDateFormat) {
209 final SimpleDateFormat sdf = (SimpleDateFormat) natural;
210 final String pattern = sdf.toPattern();
211 is24Hour = hasDesignator(pattern, 'H');
212 } else {
213 is24Hour = false;
214 }
215
216 synchronized (sLocaleLock) {
217 sIs24HourLocale = locale;
218 sIs24Hour = is24Hour;
219 }
220
221 return is24Hour;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800222 }
223
224 /**
Elliott Hughes031b5812013-04-02 11:56:23 -0700225 * Returns the best possible localized form of the given skeleton for the given
226 * locale. A skeleton is similar to, and uses the same format characters as, a Unicode
227 * <a href="http://www.unicode.org/reports/tr35/#Date_Format_Patterns">UTS #35</a>
228 * pattern.
229 *
230 * <p>One difference is that order is irrelevant. For example, "MMMMd" will return
231 * "MMMM d" in the {@code en_US} locale, but "d. MMMM" in the {@code de_CH} locale.
232 *
233 * <p>Note also in that second example that the necessary punctuation for German was
234 * added. For the same input in {@code es_ES}, we'd have even more extra text:
235 * "d 'de' MMMM".
236 *
237 * <p>This method will automatically correct for grammatical necessity. Given the
238 * same "MMMMd" input, this method will return "d LLLL" in the {@code fa_IR} locale,
239 * where stand-alone months are necessary. Lengths are preserved where meaningful,
240 * so "Md" would give a different result to "MMMd", say, except in a locale such as
241 * {@code ja_JP} where there is only one length of month.
242 *
243 * <p>This method will only return patterns that are in CLDR, and is useful whenever
244 * you know what elements you want in your format string but don't want to make your
245 * code specific to any one locale.
246 *
247 * @param locale the locale into which the skeleton should be localized
248 * @param skeleton a skeleton as described above
249 * @return a string pattern suitable for use with {@link java.text.SimpleDateFormat}.
250 */
251 public static String getBestDateTimePattern(Locale locale, String skeleton) {
Narayan Kamath2c9d2002014-06-12 13:42:05 +0100252 return ICU.getBestDateTimePattern(skeleton, locale);
Elliott Hughes031b5812013-04-02 11:56:23 -0700253 }
254
255 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800256 * Returns a {@link java.text.DateFormat} object that can format the time according
Roozbeh Pournader31504342017-07-20 12:34:06 -0700257 * to the context's locale and the user's 12-/24-hour clock preference.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800258 * @param context the application context
259 * @return the {@link java.text.DateFormat} object that properly formats the time.
260 */
Romain Guy3d1728c2012-10-31 20:31:58 -0700261 public static java.text.DateFormat getTimeFormat(Context context) {
Roozbeh Pournader31504342017-07-20 12:34:06 -0700262 final Locale locale = context.getResources().getConfiguration().locale;
263 return new java.text.SimpleDateFormat(getTimeFormatString(context), locale);
Elliott Hughescdafd372013-03-18 17:21:33 -0700264 }
265
266 /**
267 * Returns a String pattern that can be used to format the time according
Roozbeh Pournader31504342017-07-20 12:34:06 -0700268 * to the context's locale and the user's 12-/24-hour clock preference.
Elliott Hughescdafd372013-03-18 17:21:33 -0700269 * @param context the application context
270 * @hide
271 */
272 public static String getTimeFormatString(Context context) {
Selim Cinek9c4a7072014-11-21 17:44:34 +0100273 return getTimeFormatString(context, UserHandle.myUserId());
274 }
275
276 /**
277 * Returns a String pattern that can be used to format the time according
Roozbeh Pournader31504342017-07-20 12:34:06 -0700278 * to the context's locale and the user's 12-/24-hour clock preference.
Selim Cinek9c4a7072014-11-21 17:44:34 +0100279 * @param context the application context
280 * @param userHandle the user handle of the user to query the format for
281 * @hide
282 */
283 public static String getTimeFormatString(Context context, int userHandle) {
Roozbeh Pournader31504342017-07-20 12:34:06 -0700284 final LocaleData d = LocaleData.get(context.getResources().getConfiguration().locale);
Jorim Jaggi0da89b72014-11-25 15:34:56 +0100285 return is24HourFormat(context, userHandle) ? d.timeFormat_Hm : d.timeFormat_hm;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800286 }
287
288 /**
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800289 * Returns a {@link java.text.DateFormat} object that can format the date
Roozbeh Pournader31504342017-07-20 12:34:06 -0700290 * in short form according to the context's locale.
Narayan Kamathf91f06a2014-11-18 13:23:02 +0000291 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800292 * @param context the application context
293 * @return the {@link java.text.DateFormat} object that properly formats the date.
294 */
Romain Guy3d1728c2012-10-31 20:31:58 -0700295 public static java.text.DateFormat getDateFormat(Context context) {
Roozbeh Pournader31504342017-07-20 12:34:06 -0700296 final Locale locale = context.getResources().getConfiguration().locale;
297 return java.text.DateFormat.getDateInstance(java.text.DateFormat.SHORT, locale);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800298 }
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800299
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800300 /**
301 * Returns a {@link java.text.DateFormat} object that can format the date
Roozbeh Pournader31504342017-07-20 12:34:06 -0700302 * in long form (such as {@code Monday, January 3, 2000}) for the context's locale.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800303 * @param context the application context
304 * @return the {@link java.text.DateFormat} object that formats the date in long form.
305 */
Romain Guy3d1728c2012-10-31 20:31:58 -0700306 public static java.text.DateFormat getLongDateFormat(Context context) {
Roozbeh Pournader31504342017-07-20 12:34:06 -0700307 final Locale locale = context.getResources().getConfiguration().locale;
308 return java.text.DateFormat.getDateInstance(java.text.DateFormat.LONG, locale);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800309 }
310
311 /**
312 * Returns a {@link java.text.DateFormat} object that can format the date
Roozbeh Pournader31504342017-07-20 12:34:06 -0700313 * in medium form (such as {@code Jan 3, 2000}) for the context's locale.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800314 * @param context the application context
315 * @return the {@link java.text.DateFormat} object that formats the date in long form.
316 */
Romain Guy3d1728c2012-10-31 20:31:58 -0700317 public static java.text.DateFormat getMediumDateFormat(Context context) {
Roozbeh Pournader31504342017-07-20 12:34:06 -0700318 final Locale locale = context.getResources().getConfiguration().locale;
319 return java.text.DateFormat.getDateInstance(java.text.DateFormat.MEDIUM, locale);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800320 }
321
322 /**
Narayan Kamath9d68b3c2014-10-22 12:55:58 +0100323 * Gets the current date format stored as a char array. Returns a 3 element
324 * array containing the day ({@code 'd'}), month ({@code 'M'}), and year ({@code 'y'}))
325 * in the order specified by the user's format preference. Note that this order is
Elliott Hughesedd6f9e2013-04-30 11:09:53 -0700326 * <i>only</i> appropriate for all-numeric dates; spelled-out (MEDIUM and LONG)
Eric Fischer03a80172009-07-23 18:32:42 -0700327 * dates will generally contain other punctuation, spaces, or words,
328 * not just the day, month, and year, and not necessarily in the same
329 * order returned here.
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800330 */
Romain Guy3d1728c2012-10-31 20:31:58 -0700331 public static char[] getDateFormatOrder(Context context) {
Roozbeh Pournader31504342017-07-20 12:34:06 -0700332 return ICU.getDateFormatOrder(getDateFormatString(context));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800333 }
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800334
Roozbeh Pournader31504342017-07-20 12:34:06 -0700335 private static String getDateFormatString(Context context) {
336 final Locale locale = context.getResources().getConfiguration().locale;
337 java.text.DateFormat df = java.text.DateFormat.getDateInstance(
338 java.text.DateFormat.SHORT, locale);
Narayan Kamathf91f06a2014-11-18 13:23:02 +0000339 if (df instanceof SimpleDateFormat) {
340 return ((SimpleDateFormat) df).toPattern();
341 }
Eric Fischer03a80172009-07-23 18:32:42 -0700342
Narayan Kamathf91f06a2014-11-18 13:23:02 +0000343 throw new AssertionError("!(df instanceof SimpleDateFormat)");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800344 }
345
346 /**
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800347 * Given a format string and a time in milliseconds since Jan 1, 1970 GMT, returns a
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800348 * CharSequence containing the requested date.
349 * @param inFormat the format string, as described in {@link android.text.format.DateFormat}
350 * @param inTimeInMillis in milliseconds since Jan 1, 1970 GMT
351 * @return a {@link CharSequence} containing the requested text
352 */
Romain Guy3d1728c2012-10-31 20:31:58 -0700353 public static CharSequence format(CharSequence inFormat, long inTimeInMillis) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800354 return format(inFormat, new Date(inTimeInMillis));
355 }
356
357 /**
358 * Given a format string and a {@link java.util.Date} object, returns a CharSequence containing
359 * the requested date.
360 * @param inFormat the format string, as described in {@link android.text.format.DateFormat}
361 * @param inDate the date to format
362 * @return a {@link CharSequence} containing the requested text
363 */
Romain Guy3d1728c2012-10-31 20:31:58 -0700364 public static CharSequence format(CharSequence inFormat, Date inDate) {
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800365 Calendar c = new GregorianCalendar();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800366 c.setTime(inDate);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800367 return format(inFormat, c);
368 }
369
370 /**
Romain Guy3d1728c2012-10-31 20:31:58 -0700371 * Indicates whether the specified format string contains seconds.
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800372 *
Romain Guy3d1728c2012-10-31 20:31:58 -0700373 * Always returns false if the input format is null.
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800374 *
Romain Guy3d1728c2012-10-31 20:31:58 -0700375 * @param inFormat the format string, as described in {@link android.text.format.DateFormat}
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800376 *
Romain Guy3d1728c2012-10-31 20:31:58 -0700377 * @return true if the format string contains {@link #SECONDS}, false otherwise
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800378 *
Romain Guy3d1728c2012-10-31 20:31:58 -0700379 * @hide
380 */
381 public static boolean hasSeconds(CharSequence inFormat) {
Jeff Sharkey06c5f8a2012-12-04 09:53:44 -0800382 return hasDesignator(inFormat, SECONDS);
383 }
384
385 /**
386 * Test if a format string contains the given designator. Always returns
387 * {@code false} if the input format is {@code null}.
388 *
Roozbeh Pournader31504342017-07-20 12:34:06 -0700389 * Note that this is intended for searching for designators, not arbitrary
390 * characters. So searching for a literal single quote would not work correctly.
391 *
Jeff Sharkey06c5f8a2012-12-04 09:53:44 -0800392 * @hide
393 */
394 public static boolean hasDesignator(CharSequence inFormat, char designator) {
Romain Guy3d1728c2012-10-31 20:31:58 -0700395 if (inFormat == null) return false;
396
397 final int length = inFormat.length();
398
Roozbeh Pournader31504342017-07-20 12:34:06 -0700399 boolean insideQuote = false;
400 for (int i = 0; i < length; i++) {
401 final char c = inFormat.charAt(i);
Romain Guy3d1728c2012-10-31 20:31:58 -0700402 if (c == QUOTE) {
Roozbeh Pournader31504342017-07-20 12:34:06 -0700403 insideQuote = !insideQuote;
404 } else if (!insideQuote) {
405 if (c == designator) {
406 return true;
407 }
Romain Guy3d1728c2012-10-31 20:31:58 -0700408 }
409 }
410
411 return false;
412 }
413
Romain Guy3d1728c2012-10-31 20:31:58 -0700414 /**
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800415 * Given a format string and a {@link java.util.Calendar} object, returns a CharSequence
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800416 * containing the requested date.
417 * @param inFormat the format string, as described in {@link android.text.format.DateFormat}
418 * @param inDate the date to format
419 * @return a {@link CharSequence} containing the requested text
420 */
Romain Guy3d1728c2012-10-31 20:31:58 -0700421 public static CharSequence format(CharSequence inFormat, Calendar inDate) {
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800422 SpannableStringBuilder s = new SpannableStringBuilder(inFormat);
423 int count;
424
425 LocaleData localeData = LocaleData.get(Locale.getDefault());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800426
427 int len = inFormat.length();
428
429 for (int i = 0; i < len; i += count) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800430 count = 1;
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800431 int c = s.charAt(i);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800432
433 if (c == QUOTE) {
434 count = appendQuotedText(s, i, len);
435 len = s.length();
436 continue;
437 }
438
439 while ((i + count < len) && (s.charAt(i + count) == c)) {
440 count++;
441 }
442
443 String replacement;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800444 switch (c) {
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800445 case 'A':
446 case 'a':
447 replacement = localeData.amPm[inDate.get(Calendar.AM_PM) - Calendar.AM];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800448 break;
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800449 case 'd':
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800450 replacement = zeroPad(inDate.get(Calendar.DATE), count);
451 break;
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800452 case 'c':
453 case 'E':
454 replacement = getDayOfWeekString(localeData,
455 inDate.get(Calendar.DAY_OF_WEEK), count, c);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800456 break;
Elliott Hughes7a89f622013-03-11 14:49:44 -0700457 case 'K': // hour in am/pm (0-11)
458 case 'h': // hour in am/pm (1-12)
459 {
460 int hour = inDate.get(Calendar.HOUR);
461 if (c == 'h' && hour == 0) {
462 hour = 12;
463 }
464 replacement = zeroPad(hour, count);
465 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800466 break;
Elliott Hughes7a89f622013-03-11 14:49:44 -0700467 case 'H': // hour in day (0-23)
Elliott Hughesfc55c2b2013-03-18 14:59:59 -0700468 case 'k': // hour in day (1-24) [but see note below]
Elliott Hughes7a89f622013-03-11 14:49:44 -0700469 {
470 int hour = inDate.get(Calendar.HOUR_OF_DAY);
Elliott Hughesfc55c2b2013-03-18 14:59:59 -0700471 // Historically on Android 'k' was interpreted as 'H', which wasn't
472 // implemented, so pretty much all callers that want to format 24-hour
473 // times are abusing 'k'. http://b/8359981.
474 if (false && c == 'k' && hour == 0) {
Elliott Hughes7a89f622013-03-11 14:49:44 -0700475 hour = 24;
476 }
477 replacement = zeroPad(hour, count);
478 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800479 break;
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800480 case 'L':
481 case 'M':
482 replacement = getMonthString(localeData,
483 inDate.get(Calendar.MONTH), count, c);
484 break;
485 case 'm':
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800486 replacement = zeroPad(inDate.get(Calendar.MINUTE), count);
487 break;
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800488 case 's':
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800489 replacement = zeroPad(inDate.get(Calendar.SECOND), count);
490 break;
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800491 case 'y':
492 replacement = getYearString(inDate.get(Calendar.YEAR), count);
493 break;
494 case 'z':
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800495 replacement = getTimeZoneString(inDate, count);
496 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800497 default:
498 replacement = null;
499 break;
500 }
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800501
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800502 if (replacement != null) {
503 s.replace(i, i + count, replacement);
504 count = replacement.length(); // CARE: count is used in the for loop above
505 len = s.length();
506 }
507 }
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800508
509 if (inFormat instanceof Spanned) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800510 return new SpannedString(s);
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800511 } else {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800512 return s.toString();
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800513 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800514 }
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800515
516 private static String getDayOfWeekString(LocaleData ld, int day, int count, int kind) {
517 boolean standalone = (kind == 'c');
518 if (count == 5) {
519 return standalone ? ld.tinyStandAloneWeekdayNames[day] : ld.tinyWeekdayNames[day];
520 } else if (count == 4) {
521 return standalone ? ld.longStandAloneWeekdayNames[day] : ld.longWeekdayNames[day];
522 } else {
523 return standalone ? ld.shortStandAloneWeekdayNames[day] : ld.shortWeekdayNames[day];
524 }
525 }
526
527 private static String getMonthString(LocaleData ld, int month, int count, int kind) {
528 boolean standalone = (kind == 'L');
529 if (count == 5) {
530 return standalone ? ld.tinyStandAloneMonthNames[month] : ld.tinyMonthNames[month];
531 } else if (count == 4) {
532 return standalone ? ld.longStandAloneMonthNames[month] : ld.longMonthNames[month];
Elliott Hughes34de3bc2012-09-14 21:10:05 -0700533 } else if (count == 3) {
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800534 return standalone ? ld.shortStandAloneMonthNames[month] : ld.shortMonthNames[month];
Elliott Hughes34de3bc2012-09-14 21:10:05 -0700535 } else {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800536 // Calendar.JANUARY == 0, so add 1 to month.
537 return zeroPad(month+1, count);
538 }
539 }
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800540
Romain Guy3d1728c2012-10-31 20:31:58 -0700541 private static String getTimeZoneString(Calendar inDate, int count) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800542 TimeZone tz = inDate.getTimeZone();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800543 if (count < 2) { // FIXME: shouldn't this be <= 2 ?
544 return formatZoneOffset(inDate.get(Calendar.DST_OFFSET) +
545 inDate.get(Calendar.ZONE_OFFSET),
546 count);
547 } else {
548 boolean dst = inDate.get(Calendar.DST_OFFSET) != 0;
549 return tz.getDisplayName(dst, TimeZone.SHORT);
550 }
551 }
552
Romain Guy3d1728c2012-10-31 20:31:58 -0700553 private static String formatZoneOffset(int offset, int count) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800554 offset /= 1000; // milliseconds to seconds
555 StringBuilder tb = new StringBuilder();
556
557 if (offset < 0) {
558 tb.insert(0, "-");
559 offset = -offset;
560 } else {
561 tb.insert(0, "+");
562 }
563
564 int hours = offset / 3600;
565 int minutes = (offset % 3600) / 60;
566
567 tb.append(zeroPad(hours, 2));
568 tb.append(zeroPad(minutes, 2));
569 return tb.toString();
570 }
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800571
572 private static String getYearString(int year, int count) {
Elliott Hughes34de3bc2012-09-14 21:10:05 -0700573 return (count <= 2) ? zeroPad(year % 100, 2)
574 : String.format(Locale.getDefault(), "%d", year);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800575 }
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800576
Romain Guy3d1728c2012-10-31 20:31:58 -0700577 private static int appendQuotedText(SpannableStringBuilder s, int i, int len) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800578 if (i + 1 < len && s.charAt(i + 1) == QUOTE) {
579 s.delete(i, i + 1);
580 return 1;
581 }
582
583 int count = 0;
584
585 // delete leading quote
586 s.delete(i, i + 1);
587 len--;
588
589 while (i < len) {
590 char c = s.charAt(i);
591
592 if (c == QUOTE) {
593 // QUOTEQUOTE -> QUOTE
594 if (i + 1 < len && s.charAt(i + 1) == QUOTE) {
595
596 s.delete(i, i + 1);
597 len--;
598 count++;
599 i++;
600 } else {
601 // Closing QUOTE ends quoted text copying
602 s.delete(i, i + 1);
603 break;
604 }
605 } else {
606 i++;
607 count++;
608 }
609 }
610
611 return count;
612 }
613
Romain Guy3d1728c2012-10-31 20:31:58 -0700614 private static String zeroPad(int inValue, int inMinDigits) {
Elliott Hughes34de3bc2012-09-14 21:10:05 -0700615 return String.format(Locale.getDefault(), "%0" + inMinDigits + "d", inValue);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800616 }
617}