blob: 72bbb2bbc8d01f6a8612a05594c1405955382e57 [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
19import android.content.Context;
Selim Cinek9c4a7072014-11-21 17:44:34 +010020import android.os.UserHandle;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080021import android.provider.Settings;
22import android.text.SpannableStringBuilder;
23import android.text.Spanned;
24import android.text.SpannedString;
25
26import com.android.internal.R;
27
28import java.util.Calendar;
29import java.util.Date;
30import java.util.GregorianCalendar;
31import java.util.Locale;
32import java.util.TimeZone;
33import java.text.SimpleDateFormat;
34
Elliott Hughes031b5812013-04-02 11:56:23 -070035import libcore.icu.ICU;
Elliott Hughes4caba612013-01-14 15:48:27 -080036import libcore.icu.LocaleData;
37
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080038/**
Elliott Hughes8326b9a2013-03-08 15:06:14 -080039 * Utility class for producing strings with formatted date/time.
40 *
41 * <p>Most callers should avoid supplying their own format strings to this
42 * class' {@code format} methods and rely on the correctly localized ones
43 * supplied by the system. This class' factory methods return
44 * appropriately-localized {@link java.text.DateFormat} instances, suitable
45 * for both formatting and parsing dates. For the canonical documentation
46 * of format strings, see {@link java.text.SimpleDateFormat}.
47 *
Elliott Hughes031b5812013-04-02 11:56:23 -070048 * <p>In cases where the system does not provide a suitable pattern,
49 * this class offers the {@link #getBestDateTimePattern} method.
50 *
Elliott Hughesfc55c2b2013-03-18 14:59:59 -070051 * <p>The {@code format} methods in this class implement a subset of Unicode
Elliott Hughes8326b9a2013-03-08 15:06:14 -080052 * <a href="http://www.unicode.org/reports/tr35/#Date_Format_Patterns">UTS #35</a> patterns.
Elliott Hughesfc55c2b2013-03-18 14:59:59 -070053 * The subset currently supported by this class includes the following format characters:
54 * {@code acdEHhLKkLMmsyz}. Up to API level 17, only {@code adEhkMmszy} were supported.
55 * Note that this class incorrectly implements {@code k} as if it were {@code H} for backwards
56 * compatibility.
57 *
58 * <p>See {@link java.text.SimpleDateFormat} for more documentation
59 * about patterns, or if you need a more complete or correct implementation.
60 * Note that the non-{@code format} methods in this class are implemented by
61 * {@code SimpleDateFormat}.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080062 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080063public class DateFormat {
Narayan Kamath9d68b3c2014-10-22 12:55:58 +010064 /**
65 * @deprecated Use a literal {@code '} instead.
66 * @removed
67 */
Elliott Hughes8326b9a2013-03-08 15:06:14 -080068 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080069 public static final char QUOTE = '\'';
Elliott Hughes8326b9a2013-03-08 15:06:14 -080070
Narayan Kamath9d68b3c2014-10-22 12:55:58 +010071 /**
72 * @deprecated Use a literal {@code 'a'} instead.
73 * @removed
74 */
Elliott Hughes8326b9a2013-03-08 15:06:14 -080075 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080076 public static final char AM_PM = 'a';
77
Narayan Kamath9d68b3c2014-10-22 12:55:58 +010078 /**
79 * @deprecated Use a literal {@code 'a'} instead; 'A' was always equivalent to 'a'.
80 * @removed
81 */
Elliott Hughes8326b9a2013-03-08 15:06:14 -080082 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080083 public static final char CAPITAL_AM_PM = 'A';
84
Narayan Kamath9d68b3c2014-10-22 12:55:58 +010085 /**
86 * @deprecated Use a literal {@code 'd'} instead.
87 * @removed
88 */
Elliott Hughes8326b9a2013-03-08 15:06:14 -080089 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080090 public static final char DATE = 'd';
91
Narayan Kamath9d68b3c2014-10-22 12:55:58 +010092 /**
93 * @deprecated Use a literal {@code 'E'} instead.
94 * @removed
95 */
Elliott Hughes8326b9a2013-03-08 15:06:14 -080096 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080097 public static final char DAY = 'E';
98
Narayan Kamath9d68b3c2014-10-22 12:55:58 +010099 /**
100 * @deprecated Use a literal {@code 'h'} instead.
101 * @removed
102 */
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800103 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800104 public static final char HOUR = 'h';
105
Elliott Hughesfc55c2b2013-03-18 14:59:59 -0700106 /**
107 * @deprecated Use a literal {@code 'H'} (for compatibility with {@link SimpleDateFormat}
108 * and Unicode) or {@code 'k'} (for compatibility with Android releases up to and including
109 * Jelly Bean MR-1) instead. Note that the two are incompatible.
Narayan Kamath9d68b3c2014-10-22 12:55:58 +0100110 *
111 * @removed
Elliott Hughesfc55c2b2013-03-18 14:59:59 -0700112 */
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800113 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800114 public static final char HOUR_OF_DAY = 'k';
115
Narayan Kamath9d68b3c2014-10-22 12:55:58 +0100116 /**
117 * @deprecated Use a literal {@code 'm'} instead.
118 * @removed
119 */
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800120 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800121 public static final char MINUTE = 'm';
122
Narayan Kamath9d68b3c2014-10-22 12:55:58 +0100123 /**
124 * @deprecated Use a literal {@code 'M'} instead.
125 * @removed
126 */
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800127 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800128 public static final char MONTH = 'M';
129
Narayan Kamath9d68b3c2014-10-22 12:55:58 +0100130 /**
131 * @deprecated Use a literal {@code 'L'} instead.
132 * @removed
133 */
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800134 @Deprecated
Elliott Hughes34de3bc2012-09-14 21:10:05 -0700135 public static final char STANDALONE_MONTH = 'L';
136
Narayan Kamath9d68b3c2014-10-22 12:55:58 +0100137 /**
138 * @deprecated Use a literal {@code 's'} instead.
139 * @removed
140 */
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800141 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800142 public static final char SECONDS = 's';
143
Narayan Kamath9d68b3c2014-10-22 12:55:58 +0100144 /**
145 * @deprecated Use a literal {@code 'z'} instead.
146 * @removed
147 */
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800148 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800149 public static final char TIME_ZONE = 'z';
150
Narayan Kamath9d68b3c2014-10-22 12:55:58 +0100151 /**
152 * @deprecated Use a literal {@code 'y'} instead.
153 * @removed
154 */
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800155 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800156 public static final char YEAR = 'y';
157
158
159 private static final Object sLocaleLock = new Object();
160 private static Locale sIs24HourLocale;
161 private static boolean sIs24Hour;
162
163
164 /**
165 * Returns true if user preference is set to 24-hour format.
166 * @param context the context to use for the content resolver
167 * @return true if 24 hour time format is selected, false otherwise.
168 */
169 public static boolean is24HourFormat(Context context) {
Selim Cinek9c4a7072014-11-21 17:44:34 +0100170 return is24HourFormat(context, UserHandle.myUserId());
171 }
172
173 /**
174 * Returns true if user preference with the given user handle is set to 24-hour format.
175 * @param context the context to use for the content resolver
176 * @param userHandle the user handle of the user to query.
177 * @return true if 24 hour time format is selected, false otherwise.
178 *
179 * @hide
180 */
181 public static boolean is24HourFormat(Context context, int userHandle) {
182 String value = Settings.System.getStringForUser(context.getContentResolver(),
183 Settings.System.TIME_12_24, userHandle);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800184
185 if (value == null) {
186 Locale locale = context.getResources().getConfiguration().locale;
187
188 synchronized (sLocaleLock) {
189 if (sIs24HourLocale != null && sIs24HourLocale.equals(locale)) {
190 return sIs24Hour;
191 }
192 }
193
194 java.text.DateFormat natural =
Selim Cinek9c4a7072014-11-21 17:44:34 +0100195 java.text.DateFormat.getTimeInstance(java.text.DateFormat.LONG, locale);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800196
197 if (natural instanceof SimpleDateFormat) {
198 SimpleDateFormat sdf = (SimpleDateFormat) natural;
199 String pattern = sdf.toPattern();
200
201 if (pattern.indexOf('H') >= 0) {
202 value = "24";
203 } else {
204 value = "12";
205 }
206 } else {
207 value = "12";
208 }
209
210 synchronized (sLocaleLock) {
211 sIs24HourLocale = locale;
Romain Guy3d1728c2012-10-31 20:31:58 -0700212 sIs24Hour = value.equals("24");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800213 }
Romain Guy3d1728c2012-10-31 20:31:58 -0700214
215 return sIs24Hour;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800216 }
217
Romain Guy3d1728c2012-10-31 20:31:58 -0700218 return value.equals("24");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800219 }
220
221 /**
Elliott Hughes031b5812013-04-02 11:56:23 -0700222 * Returns the best possible localized form of the given skeleton for the given
223 * locale. A skeleton is similar to, and uses the same format characters as, a Unicode
224 * <a href="http://www.unicode.org/reports/tr35/#Date_Format_Patterns">UTS #35</a>
225 * pattern.
226 *
227 * <p>One difference is that order is irrelevant. For example, "MMMMd" will return
228 * "MMMM d" in the {@code en_US} locale, but "d. MMMM" in the {@code de_CH} locale.
229 *
230 * <p>Note also in that second example that the necessary punctuation for German was
231 * added. For the same input in {@code es_ES}, we'd have even more extra text:
232 * "d 'de' MMMM".
233 *
234 * <p>This method will automatically correct for grammatical necessity. Given the
235 * same "MMMMd" input, this method will return "d LLLL" in the {@code fa_IR} locale,
236 * where stand-alone months are necessary. Lengths are preserved where meaningful,
237 * so "Md" would give a different result to "MMMd", say, except in a locale such as
238 * {@code ja_JP} where there is only one length of month.
239 *
240 * <p>This method will only return patterns that are in CLDR, and is useful whenever
241 * you know what elements you want in your format string but don't want to make your
242 * code specific to any one locale.
243 *
244 * @param locale the locale into which the skeleton should be localized
245 * @param skeleton a skeleton as described above
246 * @return a string pattern suitable for use with {@link java.text.SimpleDateFormat}.
247 */
248 public static String getBestDateTimePattern(Locale locale, String skeleton) {
Narayan Kamath2c9d2002014-06-12 13:42:05 +0100249 return ICU.getBestDateTimePattern(skeleton, locale);
Elliott Hughes031b5812013-04-02 11:56:23 -0700250 }
251
252 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800253 * Returns a {@link java.text.DateFormat} object that can format the time according
Eric Fischeraf0e7a732009-06-11 13:37:04 -0700254 * to the current locale and the user's 12-/24-hour clock preference.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800255 * @param context the application context
256 * @return the {@link java.text.DateFormat} object that properly formats the time.
257 */
Romain Guy3d1728c2012-10-31 20:31:58 -0700258 public static java.text.DateFormat getTimeFormat(Context context) {
Elliott Hughescdafd372013-03-18 17:21:33 -0700259 return new java.text.SimpleDateFormat(getTimeFormatString(context));
260 }
261
262 /**
263 * Returns a String pattern that can be used to format the time according
264 * to the current locale and the user's 12-/24-hour clock preference.
265 * @param context the application context
266 * @hide
267 */
268 public static String getTimeFormatString(Context context) {
Selim Cinek9c4a7072014-11-21 17:44:34 +0100269 return getTimeFormatString(context, UserHandle.myUserId());
270 }
271
272 /**
273 * Returns a String pattern that can be used to format the time according
274 * to the current locale and the user's 12-/24-hour clock preference.
275 * @param context the application context
276 * @param userHandle the user handle of the user to query the format for
277 * @hide
278 */
279 public static String getTimeFormatString(Context context, int userHandle) {
Elliott Hughes4caba612013-01-14 15:48:27 -0800280 LocaleData d = LocaleData.get(context.getResources().getConfiguration().locale);
Selim Cinek9c4a7072014-11-21 17:44:34 +0100281 return is24HourFormat(context, userHandle) ? d.timeFormat24 : d.timeFormat12;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800282 }
283
284 /**
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800285 * Returns a {@link java.text.DateFormat} object that can format the date
Eric Fischer5bd644c2009-05-12 16:29:46 -0700286 * in short form (such as 12/31/1999) according
Eric Fischer32876952009-06-11 18:11:09 -0700287 * to the current locale and the user's date-order preference.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800288 * @param context the application context
289 * @return the {@link java.text.DateFormat} object that properly formats the date.
290 */
Romain Guy3d1728c2012-10-31 20:31:58 -0700291 public static java.text.DateFormat getDateFormat(Context context) {
Kenny Guy692356b2014-09-04 18:16:17 +0100292 String value = Settings.System.getString(context.getContentResolver(),
293 Settings.System.DATE_FORMAT);
Eric Fischer32876952009-06-11 18:11:09 -0700294
295 return getDateFormatForSetting(context, value);
296 }
297
298 /**
299 * Returns a {@link java.text.DateFormat} object to format the date
300 * as if the date format setting were set to <code>value</code>,
301 * including null to use the locale's default format.
302 * @param context the application context
303 * @param value the date format setting string to interpret for
304 * the current locale
305 * @hide
306 */
307 public static java.text.DateFormat getDateFormatForSetting(Context context,
308 String value) {
Eric Fischer03a80172009-07-23 18:32:42 -0700309 String format = getDateFormatStringForSetting(context, value);
Eric Fischer03a80172009-07-23 18:32:42 -0700310 return new java.text.SimpleDateFormat(format);
311 }
312
313 private static String getDateFormatStringForSetting(Context context, String value) {
Eric Fischer32876952009-06-11 18:11:09 -0700314 if (value != null) {
315 int month = value.indexOf('M');
316 int day = value.indexOf('d');
317 int year = value.indexOf('y');
318
319 if (month >= 0 && day >= 0 && year >= 0) {
320 String template = context.getString(R.string.numeric_date_template);
Eric Fischer03a80172009-07-23 18:32:42 -0700321 if (year < month && year < day) {
Eric Fischer32876952009-06-11 18:11:09 -0700322 if (month < day) {
323 value = String.format(template, "yyyy", "MM", "dd");
324 } else {
325 value = String.format(template, "yyyy", "dd", "MM");
326 }
327 } else if (month < day) {
328 if (day < year) {
329 value = String.format(template, "MM", "dd", "yyyy");
330 } else { // unlikely
331 value = String.format(template, "MM", "yyyy", "dd");
332 }
333 } else { // day < month
334 if (month < year) {
335 value = String.format(template, "dd", "MM", "yyyy");
336 } else { // unlikely
337 value = String.format(template, "dd", "yyyy", "MM");
338 }
339 }
340
Eric Fischer03a80172009-07-23 18:32:42 -0700341 return value;
Eric Fischer32876952009-06-11 18:11:09 -0700342 }
343 }
344
howardb5ad24512013-06-16 14:16:53 +0100345 // The setting is not set; use the locale's default.
346 LocaleData d = LocaleData.get(context.getResources().getConfiguration().locale);
347 return d.shortDateFormat4;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800348 }
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800349
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800350 /**
351 * Returns a {@link java.text.DateFormat} object that can format the date
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800352 * in long form (such as {@code Monday, January 3, 2000}) for the current locale.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800353 * @param context the application context
354 * @return the {@link java.text.DateFormat} object that formats the date in long form.
355 */
Romain Guy3d1728c2012-10-31 20:31:58 -0700356 public static java.text.DateFormat getLongDateFormat(Context context) {
Eric Fischer5bd644c2009-05-12 16:29:46 -0700357 return java.text.DateFormat.getDateInstance(java.text.DateFormat.LONG);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800358 }
359
360 /**
361 * Returns a {@link java.text.DateFormat} object that can format the date
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800362 * in medium form (such as {@code Jan 3, 2000}) for the current locale.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800363 * @param context the application context
364 * @return the {@link java.text.DateFormat} object that formats the date in long form.
365 */
Romain Guy3d1728c2012-10-31 20:31:58 -0700366 public static java.text.DateFormat getMediumDateFormat(Context context) {
Eric Fischer5bd644c2009-05-12 16:29:46 -0700367 return java.text.DateFormat.getDateInstance(java.text.DateFormat.MEDIUM);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800368 }
369
370 /**
Narayan Kamath9d68b3c2014-10-22 12:55:58 +0100371 * Gets the current date format stored as a char array. Returns a 3 element
372 * array containing the day ({@code 'd'}), month ({@code 'M'}), and year ({@code 'y'}))
373 * in the order specified by the user's format preference. Note that this order is
Elliott Hughesedd6f9e2013-04-30 11:09:53 -0700374 * <i>only</i> appropriate for all-numeric dates; spelled-out (MEDIUM and LONG)
Eric Fischer03a80172009-07-23 18:32:42 -0700375 * dates will generally contain other punctuation, spaces, or words,
376 * not just the day, month, and year, and not necessarily in the same
377 * order returned here.
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800378 */
Romain Guy3d1728c2012-10-31 20:31:58 -0700379 public static char[] getDateFormatOrder(Context context) {
Elliott Hughesedd6f9e2013-04-30 11:09:53 -0700380 return ICU.getDateFormatOrder(getDateFormatString(context));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800381 }
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800382
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800383 private static String getDateFormatString(Context context) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800384 String value = Settings.System.getString(context.getContentResolver(),
385 Settings.System.DATE_FORMAT);
Eric Fischer03a80172009-07-23 18:32:42 -0700386
387 return getDateFormatStringForSetting(context, value);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800388 }
389
390 /**
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800391 * 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 -0800392 * CharSequence containing the requested date.
393 * @param inFormat the format string, as described in {@link android.text.format.DateFormat}
394 * @param inTimeInMillis in milliseconds since Jan 1, 1970 GMT
395 * @return a {@link CharSequence} containing the requested text
396 */
Romain Guy3d1728c2012-10-31 20:31:58 -0700397 public static CharSequence format(CharSequence inFormat, long inTimeInMillis) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800398 return format(inFormat, new Date(inTimeInMillis));
399 }
400
401 /**
402 * Given a format string and a {@link java.util.Date} object, returns a CharSequence containing
403 * the requested date.
404 * @param inFormat the format string, as described in {@link android.text.format.DateFormat}
405 * @param inDate the date to format
406 * @return a {@link CharSequence} containing the requested text
407 */
Romain Guy3d1728c2012-10-31 20:31:58 -0700408 public static CharSequence format(CharSequence inFormat, Date inDate) {
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800409 Calendar c = new GregorianCalendar();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800410 c.setTime(inDate);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800411 return format(inFormat, c);
412 }
413
414 /**
Romain Guy3d1728c2012-10-31 20:31:58 -0700415 * Indicates whether the specified format string contains seconds.
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800416 *
Romain Guy3d1728c2012-10-31 20:31:58 -0700417 * Always returns false if the input format is null.
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800418 *
Romain Guy3d1728c2012-10-31 20:31:58 -0700419 * @param inFormat the format string, as described in {@link android.text.format.DateFormat}
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800420 *
Romain Guy3d1728c2012-10-31 20:31:58 -0700421 * @return true if the format string contains {@link #SECONDS}, false otherwise
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800422 *
Romain Guy3d1728c2012-10-31 20:31:58 -0700423 * @hide
424 */
425 public static boolean hasSeconds(CharSequence inFormat) {
Jeff Sharkey06c5f8a2012-12-04 09:53:44 -0800426 return hasDesignator(inFormat, SECONDS);
427 }
428
429 /**
430 * Test if a format string contains the given designator. Always returns
431 * {@code false} if the input format is {@code null}.
432 *
433 * @hide
434 */
435 public static boolean hasDesignator(CharSequence inFormat, char designator) {
Romain Guy3d1728c2012-10-31 20:31:58 -0700436 if (inFormat == null) return false;
437
438 final int length = inFormat.length();
439
440 int c;
441 int count;
442
443 for (int i = 0; i < length; i += count) {
444 count = 1;
445 c = inFormat.charAt(i);
446
447 if (c == QUOTE) {
448 count = skipQuotedText(inFormat, i, length);
Jeff Sharkey06c5f8a2012-12-04 09:53:44 -0800449 } else if (c == designator) {
Romain Guy3d1728c2012-10-31 20:31:58 -0700450 return true;
451 }
452 }
453
454 return false;
455 }
456
457 private static int skipQuotedText(CharSequence s, int i, int len) {
458 if (i + 1 < len && s.charAt(i + 1) == QUOTE) {
459 return 2;
460 }
461
462 int count = 1;
463 // skip leading quote
464 i++;
465
466 while (i < len) {
467 char c = s.charAt(i);
468
469 if (c == QUOTE) {
470 count++;
471 // QUOTEQUOTE -> QUOTE
472 if (i + 1 < len && s.charAt(i + 1) == QUOTE) {
473 i++;
474 } else {
475 break;
476 }
477 } else {
478 i++;
479 count++;
480 }
481 }
482
483 return count;
484 }
485
486 /**
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800487 * Given a format string and a {@link java.util.Calendar} object, returns a CharSequence
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800488 * containing the requested date.
489 * @param inFormat the format string, as described in {@link android.text.format.DateFormat}
490 * @param inDate the date to format
491 * @return a {@link CharSequence} containing the requested text
492 */
Romain Guy3d1728c2012-10-31 20:31:58 -0700493 public static CharSequence format(CharSequence inFormat, Calendar inDate) {
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800494 SpannableStringBuilder s = new SpannableStringBuilder(inFormat);
495 int count;
496
497 LocaleData localeData = LocaleData.get(Locale.getDefault());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800498
499 int len = inFormat.length();
500
501 for (int i = 0; i < len; i += count) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800502 count = 1;
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800503 int c = s.charAt(i);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800504
505 if (c == QUOTE) {
506 count = appendQuotedText(s, i, len);
507 len = s.length();
508 continue;
509 }
510
511 while ((i + count < len) && (s.charAt(i + count) == c)) {
512 count++;
513 }
514
515 String replacement;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800516 switch (c) {
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800517 case 'A':
518 case 'a':
519 replacement = localeData.amPm[inDate.get(Calendar.AM_PM) - Calendar.AM];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800520 break;
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800521 case 'd':
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800522 replacement = zeroPad(inDate.get(Calendar.DATE), count);
523 break;
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800524 case 'c':
525 case 'E':
526 replacement = getDayOfWeekString(localeData,
527 inDate.get(Calendar.DAY_OF_WEEK), count, c);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800528 break;
Elliott Hughes7a89f622013-03-11 14:49:44 -0700529 case 'K': // hour in am/pm (0-11)
530 case 'h': // hour in am/pm (1-12)
531 {
532 int hour = inDate.get(Calendar.HOUR);
533 if (c == 'h' && hour == 0) {
534 hour = 12;
535 }
536 replacement = zeroPad(hour, count);
537 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800538 break;
Elliott Hughes7a89f622013-03-11 14:49:44 -0700539 case 'H': // hour in day (0-23)
Elliott Hughesfc55c2b2013-03-18 14:59:59 -0700540 case 'k': // hour in day (1-24) [but see note below]
Elliott Hughes7a89f622013-03-11 14:49:44 -0700541 {
542 int hour = inDate.get(Calendar.HOUR_OF_DAY);
Elliott Hughesfc55c2b2013-03-18 14:59:59 -0700543 // Historically on Android 'k' was interpreted as 'H', which wasn't
544 // implemented, so pretty much all callers that want to format 24-hour
545 // times are abusing 'k'. http://b/8359981.
546 if (false && c == 'k' && hour == 0) {
Elliott Hughes7a89f622013-03-11 14:49:44 -0700547 hour = 24;
548 }
549 replacement = zeroPad(hour, count);
550 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800551 break;
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800552 case 'L':
553 case 'M':
554 replacement = getMonthString(localeData,
555 inDate.get(Calendar.MONTH), count, c);
556 break;
557 case 'm':
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800558 replacement = zeroPad(inDate.get(Calendar.MINUTE), count);
559 break;
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800560 case 's':
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800561 replacement = zeroPad(inDate.get(Calendar.SECOND), count);
562 break;
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800563 case 'y':
564 replacement = getYearString(inDate.get(Calendar.YEAR), count);
565 break;
566 case 'z':
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800567 replacement = getTimeZoneString(inDate, count);
568 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800569 default:
570 replacement = null;
571 break;
572 }
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800573
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800574 if (replacement != null) {
575 s.replace(i, i + count, replacement);
576 count = replacement.length(); // CARE: count is used in the for loop above
577 len = s.length();
578 }
579 }
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800580
581 if (inFormat instanceof Spanned) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800582 return new SpannedString(s);
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800583 } else {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800584 return s.toString();
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800585 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800586 }
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800587
588 private static String getDayOfWeekString(LocaleData ld, int day, int count, int kind) {
589 boolean standalone = (kind == 'c');
590 if (count == 5) {
591 return standalone ? ld.tinyStandAloneWeekdayNames[day] : ld.tinyWeekdayNames[day];
592 } else if (count == 4) {
593 return standalone ? ld.longStandAloneWeekdayNames[day] : ld.longWeekdayNames[day];
594 } else {
595 return standalone ? ld.shortStandAloneWeekdayNames[day] : ld.shortWeekdayNames[day];
596 }
597 }
598
599 private static String getMonthString(LocaleData ld, int month, int count, int kind) {
600 boolean standalone = (kind == 'L');
601 if (count == 5) {
602 return standalone ? ld.tinyStandAloneMonthNames[month] : ld.tinyMonthNames[month];
603 } else if (count == 4) {
604 return standalone ? ld.longStandAloneMonthNames[month] : ld.longMonthNames[month];
Elliott Hughes34de3bc2012-09-14 21:10:05 -0700605 } else if (count == 3) {
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800606 return standalone ? ld.shortStandAloneMonthNames[month] : ld.shortMonthNames[month];
Elliott Hughes34de3bc2012-09-14 21:10:05 -0700607 } else {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800608 // Calendar.JANUARY == 0, so add 1 to month.
609 return zeroPad(month+1, count);
610 }
611 }
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800612
Romain Guy3d1728c2012-10-31 20:31:58 -0700613 private static String getTimeZoneString(Calendar inDate, int count) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800614 TimeZone tz = inDate.getTimeZone();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800615 if (count < 2) { // FIXME: shouldn't this be <= 2 ?
616 return formatZoneOffset(inDate.get(Calendar.DST_OFFSET) +
617 inDate.get(Calendar.ZONE_OFFSET),
618 count);
619 } else {
620 boolean dst = inDate.get(Calendar.DST_OFFSET) != 0;
621 return tz.getDisplayName(dst, TimeZone.SHORT);
622 }
623 }
624
Romain Guy3d1728c2012-10-31 20:31:58 -0700625 private static String formatZoneOffset(int offset, int count) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800626 offset /= 1000; // milliseconds to seconds
627 StringBuilder tb = new StringBuilder();
628
629 if (offset < 0) {
630 tb.insert(0, "-");
631 offset = -offset;
632 } else {
633 tb.insert(0, "+");
634 }
635
636 int hours = offset / 3600;
637 int minutes = (offset % 3600) / 60;
638
639 tb.append(zeroPad(hours, 2));
640 tb.append(zeroPad(minutes, 2));
641 return tb.toString();
642 }
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800643
644 private static String getYearString(int year, int count) {
Elliott Hughes34de3bc2012-09-14 21:10:05 -0700645 return (count <= 2) ? zeroPad(year % 100, 2)
646 : String.format(Locale.getDefault(), "%d", year);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800647 }
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800648
Romain Guy3d1728c2012-10-31 20:31:58 -0700649 private static int appendQuotedText(SpannableStringBuilder s, int i, int len) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800650 if (i + 1 < len && s.charAt(i + 1) == QUOTE) {
651 s.delete(i, i + 1);
652 return 1;
653 }
654
655 int count = 0;
656
657 // delete leading quote
658 s.delete(i, i + 1);
659 len--;
660
661 while (i < len) {
662 char c = s.charAt(i);
663
664 if (c == QUOTE) {
665 // QUOTEQUOTE -> QUOTE
666 if (i + 1 < len && s.charAt(i + 1) == QUOTE) {
667
668 s.delete(i, i + 1);
669 len--;
670 count++;
671 i++;
672 } else {
673 // Closing QUOTE ends quoted text copying
674 s.delete(i, i + 1);
675 break;
676 }
677 } else {
678 i++;
679 count++;
680 }
681 }
682
683 return count;
684 }
685
Romain Guy3d1728c2012-10-31 20:31:58 -0700686 private static String zeroPad(int inValue, int inMinDigits) {
Elliott Hughes34de3bc2012-09-14 21:10:05 -0700687 return String.format(Locale.getDefault(), "%0" + inMinDigits + "d", inValue);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800688 }
689}