blob: b5a8acaf674f2fe07d6c75bd1214d6f5d1b2ce2b [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
Aurimas Liutikas4037d512016-10-11 17:20:06 -070026import libcore.icu.ICU;
27import libcore.icu.LocaleData;
28
29import java.text.SimpleDateFormat;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080030import java.util.Calendar;
31import java.util.Date;
32import java.util.GregorianCalendar;
33import java.util.Locale;
34import java.util.TimeZone;
Elliott Hughes4caba612013-01-14 15:48:27 -080035
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080036/**
Elliott Hughes8326b9a2013-03-08 15:06:14 -080037 * Utility class for producing strings with formatted date/time.
38 *
39 * <p>Most callers should avoid supplying their own format strings to this
40 * class' {@code format} methods and rely on the correctly localized ones
41 * supplied by the system. This class' factory methods return
42 * appropriately-localized {@link java.text.DateFormat} instances, suitable
43 * for both formatting and parsing dates. For the canonical documentation
44 * of format strings, see {@link java.text.SimpleDateFormat}.
45 *
Elliott Hughes031b5812013-04-02 11:56:23 -070046 * <p>In cases where the system does not provide a suitable pattern,
47 * this class offers the {@link #getBestDateTimePattern} method.
48 *
Elliott Hughesfc55c2b2013-03-18 14:59:59 -070049 * <p>The {@code format} methods in this class implement a subset of Unicode
Elliott Hughes8326b9a2013-03-08 15:06:14 -080050 * <a href="http://www.unicode.org/reports/tr35/#Date_Format_Patterns">UTS #35</a> patterns.
Elliott Hughesfc55c2b2013-03-18 14:59:59 -070051 * The subset currently supported by this class includes the following format characters:
52 * {@code acdEHhLKkLMmsyz}. Up to API level 17, only {@code adEhkMmszy} were supported.
53 * Note that this class incorrectly implements {@code k} as if it were {@code H} for backwards
54 * compatibility.
55 *
56 * <p>See {@link java.text.SimpleDateFormat} for more documentation
57 * about patterns, or if you need a more complete or correct implementation.
58 * Note that the non-{@code format} methods in this class are implemented by
59 * {@code SimpleDateFormat}.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080060 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080061public class DateFormat {
Narayan Kamath9d68b3c2014-10-22 12:55:58 +010062 /**
63 * @deprecated Use a literal {@code '} instead.
64 * @removed
65 */
Elliott Hughes8326b9a2013-03-08 15:06:14 -080066 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080067 public static final char QUOTE = '\'';
Elliott Hughes8326b9a2013-03-08 15:06:14 -080068
Narayan Kamath9d68b3c2014-10-22 12:55:58 +010069 /**
70 * @deprecated Use a literal {@code 'a'} instead.
71 * @removed
72 */
Elliott Hughes8326b9a2013-03-08 15:06:14 -080073 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080074 public static final char AM_PM = 'a';
75
Narayan Kamath9d68b3c2014-10-22 12:55:58 +010076 /**
77 * @deprecated Use a literal {@code 'a'} instead; 'A' was always equivalent to 'a'.
78 * @removed
79 */
Elliott Hughes8326b9a2013-03-08 15:06:14 -080080 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080081 public static final char CAPITAL_AM_PM = 'A';
82
Narayan Kamath9d68b3c2014-10-22 12:55:58 +010083 /**
84 * @deprecated Use a literal {@code 'd'} instead.
85 * @removed
86 */
Elliott Hughes8326b9a2013-03-08 15:06:14 -080087 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080088 public static final char DATE = 'd';
89
Narayan Kamath9d68b3c2014-10-22 12:55:58 +010090 /**
91 * @deprecated Use a literal {@code 'E'} instead.
92 * @removed
93 */
Elliott Hughes8326b9a2013-03-08 15:06:14 -080094 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080095 public static final char DAY = 'E';
96
Narayan Kamath9d68b3c2014-10-22 12:55:58 +010097 /**
98 * @deprecated Use a literal {@code 'h'} instead.
99 * @removed
100 */
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800101 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800102 public static final char HOUR = 'h';
103
Elliott Hughesfc55c2b2013-03-18 14:59:59 -0700104 /**
105 * @deprecated Use a literal {@code 'H'} (for compatibility with {@link SimpleDateFormat}
106 * and Unicode) or {@code 'k'} (for compatibility with Android releases up to and including
107 * Jelly Bean MR-1) instead. Note that the two are incompatible.
Narayan Kamath9d68b3c2014-10-22 12:55:58 +0100108 *
109 * @removed
Elliott Hughesfc55c2b2013-03-18 14:59:59 -0700110 */
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800111 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800112 public static final char HOUR_OF_DAY = 'k';
113
Narayan Kamath9d68b3c2014-10-22 12:55:58 +0100114 /**
115 * @deprecated Use a literal {@code 'm'} instead.
116 * @removed
117 */
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800118 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800119 public static final char MINUTE = 'm';
120
Narayan Kamath9d68b3c2014-10-22 12:55:58 +0100121 /**
122 * @deprecated Use a literal {@code 'M'} instead.
123 * @removed
124 */
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800125 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800126 public static final char MONTH = 'M';
127
Narayan Kamath9d68b3c2014-10-22 12:55:58 +0100128 /**
129 * @deprecated Use a literal {@code 'L'} instead.
130 * @removed
131 */
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800132 @Deprecated
Elliott Hughes34de3bc2012-09-14 21:10:05 -0700133 public static final char STANDALONE_MONTH = 'L';
134
Narayan Kamath9d68b3c2014-10-22 12:55:58 +0100135 /**
136 * @deprecated Use a literal {@code 's'} instead.
137 * @removed
138 */
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800139 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800140 public static final char SECONDS = 's';
141
Narayan Kamath9d68b3c2014-10-22 12:55:58 +0100142 /**
143 * @deprecated Use a literal {@code 'z'} instead.
144 * @removed
145 */
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800146 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800147 public static final char TIME_ZONE = 'z';
148
Narayan Kamath9d68b3c2014-10-22 12:55:58 +0100149 /**
150 * @deprecated Use a literal {@code 'y'} instead.
151 * @removed
152 */
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800153 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800154 public static final char YEAR = 'y';
155
156
157 private static final Object sLocaleLock = new Object();
158 private static Locale sIs24HourLocale;
159 private static boolean sIs24Hour;
160
161
162 /**
163 * Returns true if user preference is set to 24-hour format.
164 * @param context the context to use for the content resolver
165 * @return true if 24 hour time format is selected, false otherwise.
166 */
167 public static boolean is24HourFormat(Context context) {
Selim Cinek9c4a7072014-11-21 17:44:34 +0100168 return is24HourFormat(context, UserHandle.myUserId());
169 }
170
171 /**
172 * Returns true if user preference with the given user handle is set to 24-hour format.
173 * @param context the context to use for the content resolver
174 * @param userHandle the user handle of the user to query.
175 * @return true if 24 hour time format is selected, false otherwise.
176 *
177 * @hide
178 */
179 public static boolean is24HourFormat(Context context, int userHandle) {
180 String value = Settings.System.getStringForUser(context.getContentResolver(),
181 Settings.System.TIME_12_24, userHandle);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800182
183 if (value == null) {
184 Locale locale = context.getResources().getConfiguration().locale;
185
186 synchronized (sLocaleLock) {
187 if (sIs24HourLocale != null && sIs24HourLocale.equals(locale)) {
188 return sIs24Hour;
189 }
190 }
191
192 java.text.DateFormat natural =
Selim Cinek9c4a7072014-11-21 17:44:34 +0100193 java.text.DateFormat.getTimeInstance(java.text.DateFormat.LONG, locale);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800194
195 if (natural instanceof SimpleDateFormat) {
196 SimpleDateFormat sdf = (SimpleDateFormat) natural;
197 String pattern = sdf.toPattern();
198
199 if (pattern.indexOf('H') >= 0) {
200 value = "24";
201 } else {
202 value = "12";
203 }
204 } else {
205 value = "12";
206 }
207
208 synchronized (sLocaleLock) {
209 sIs24HourLocale = locale;
Romain Guy3d1728c2012-10-31 20:31:58 -0700210 sIs24Hour = value.equals("24");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800211 }
Romain Guy3d1728c2012-10-31 20:31:58 -0700212
213 return sIs24Hour;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800214 }
215
Romain Guy3d1728c2012-10-31 20:31:58 -0700216 return value.equals("24");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800217 }
218
219 /**
Elliott Hughes031b5812013-04-02 11:56:23 -0700220 * Returns the best possible localized form of the given skeleton for the given
221 * locale. A skeleton is similar to, and uses the same format characters as, a Unicode
222 * <a href="http://www.unicode.org/reports/tr35/#Date_Format_Patterns">UTS #35</a>
223 * pattern.
224 *
225 * <p>One difference is that order is irrelevant. For example, "MMMMd" will return
226 * "MMMM d" in the {@code en_US} locale, but "d. MMMM" in the {@code de_CH} locale.
227 *
228 * <p>Note also in that second example that the necessary punctuation for German was
229 * added. For the same input in {@code es_ES}, we'd have even more extra text:
230 * "d 'de' MMMM".
231 *
232 * <p>This method will automatically correct for grammatical necessity. Given the
233 * same "MMMMd" input, this method will return "d LLLL" in the {@code fa_IR} locale,
234 * where stand-alone months are necessary. Lengths are preserved where meaningful,
235 * so "Md" would give a different result to "MMMd", say, except in a locale such as
236 * {@code ja_JP} where there is only one length of month.
237 *
238 * <p>This method will only return patterns that are in CLDR, and is useful whenever
239 * you know what elements you want in your format string but don't want to make your
240 * code specific to any one locale.
241 *
242 * @param locale the locale into which the skeleton should be localized
243 * @param skeleton a skeleton as described above
244 * @return a string pattern suitable for use with {@link java.text.SimpleDateFormat}.
245 */
246 public static String getBestDateTimePattern(Locale locale, String skeleton) {
Narayan Kamath2c9d2002014-06-12 13:42:05 +0100247 return ICU.getBestDateTimePattern(skeleton, locale);
Elliott Hughes031b5812013-04-02 11:56:23 -0700248 }
249
250 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800251 * Returns a {@link java.text.DateFormat} object that can format the time according
Eric Fischeraf0e7a732009-06-11 13:37:04 -0700252 * to the current locale and the user's 12-/24-hour clock preference.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800253 * @param context the application context
254 * @return the {@link java.text.DateFormat} object that properly formats the time.
255 */
Romain Guy3d1728c2012-10-31 20:31:58 -0700256 public static java.text.DateFormat getTimeFormat(Context context) {
Elliott Hughescdafd372013-03-18 17:21:33 -0700257 return new java.text.SimpleDateFormat(getTimeFormatString(context));
258 }
259
260 /**
261 * Returns a String pattern that can be used to format the time according
262 * to the current locale and the user's 12-/24-hour clock preference.
263 * @param context the application context
264 * @hide
265 */
266 public static String getTimeFormatString(Context context) {
Selim Cinek9c4a7072014-11-21 17:44:34 +0100267 return getTimeFormatString(context, UserHandle.myUserId());
268 }
269
270 /**
271 * Returns a String pattern that can be used to format the time according
272 * to the current locale and the user's 12-/24-hour clock preference.
273 * @param context the application context
274 * @param userHandle the user handle of the user to query the format for
275 * @hide
276 */
277 public static String getTimeFormatString(Context context, int userHandle) {
Elliott Hughes4caba612013-01-14 15:48:27 -0800278 LocaleData d = LocaleData.get(context.getResources().getConfiguration().locale);
Jorim Jaggi0da89b72014-11-25 15:34:56 +0100279 return is24HourFormat(context, userHandle) ? d.timeFormat_Hm : d.timeFormat_hm;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800280 }
281
282 /**
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800283 * Returns a {@link java.text.DateFormat} object that can format the date
Narayan Kamathf91f06a2014-11-18 13:23:02 +0000284 * in short form according to the current locale.
285 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800286 * @param context the application context
287 * @return the {@link java.text.DateFormat} object that properly formats the date.
288 */
Romain Guy3d1728c2012-10-31 20:31:58 -0700289 public static java.text.DateFormat getDateFormat(Context context) {
Narayan Kamathf91f06a2014-11-18 13:23:02 +0000290 return java.text.DateFormat.getDateInstance(java.text.DateFormat.SHORT);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800291 }
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800292
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800293 /**
294 * Returns a {@link java.text.DateFormat} object that can format the date
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800295 * in long form (such as {@code Monday, January 3, 2000}) for the current locale.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800296 * @param context the application context
297 * @return the {@link java.text.DateFormat} object that formats the date in long form.
298 */
Romain Guy3d1728c2012-10-31 20:31:58 -0700299 public static java.text.DateFormat getLongDateFormat(Context context) {
Eric Fischer5bd644c2009-05-12 16:29:46 -0700300 return java.text.DateFormat.getDateInstance(java.text.DateFormat.LONG);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800301 }
302
303 /**
304 * Returns a {@link java.text.DateFormat} object that can format the date
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800305 * in medium form (such as {@code Jan 3, 2000}) for the current locale.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800306 * @param context the application context
307 * @return the {@link java.text.DateFormat} object that formats the date in long form.
308 */
Romain Guy3d1728c2012-10-31 20:31:58 -0700309 public static java.text.DateFormat getMediumDateFormat(Context context) {
Eric Fischer5bd644c2009-05-12 16:29:46 -0700310 return java.text.DateFormat.getDateInstance(java.text.DateFormat.MEDIUM);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800311 }
312
313 /**
Narayan Kamath9d68b3c2014-10-22 12:55:58 +0100314 * Gets the current date format stored as a char array. Returns a 3 element
315 * array containing the day ({@code 'd'}), month ({@code 'M'}), and year ({@code 'y'}))
316 * in the order specified by the user's format preference. Note that this order is
Elliott Hughesedd6f9e2013-04-30 11:09:53 -0700317 * <i>only</i> appropriate for all-numeric dates; spelled-out (MEDIUM and LONG)
Eric Fischer03a80172009-07-23 18:32:42 -0700318 * dates will generally contain other punctuation, spaces, or words,
319 * not just the day, month, and year, and not necessarily in the same
320 * order returned here.
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800321 */
Romain Guy3d1728c2012-10-31 20:31:58 -0700322 public static char[] getDateFormatOrder(Context context) {
Narayan Kamathf91f06a2014-11-18 13:23:02 +0000323 return ICU.getDateFormatOrder(getDateFormatString());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800324 }
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800325
Narayan Kamathf91f06a2014-11-18 13:23:02 +0000326 private static String getDateFormatString() {
327 java.text.DateFormat df = java.text.DateFormat.getDateInstance(java.text.DateFormat.SHORT);
328 if (df instanceof SimpleDateFormat) {
329 return ((SimpleDateFormat) df).toPattern();
330 }
Eric Fischer03a80172009-07-23 18:32:42 -0700331
Narayan Kamathf91f06a2014-11-18 13:23:02 +0000332 throw new AssertionError("!(df instanceof SimpleDateFormat)");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800333 }
334
335 /**
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800336 * 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 -0800337 * CharSequence containing the requested date.
338 * @param inFormat the format string, as described in {@link android.text.format.DateFormat}
339 * @param inTimeInMillis in milliseconds since Jan 1, 1970 GMT
340 * @return a {@link CharSequence} containing the requested text
341 */
Romain Guy3d1728c2012-10-31 20:31:58 -0700342 public static CharSequence format(CharSequence inFormat, long inTimeInMillis) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800343 return format(inFormat, new Date(inTimeInMillis));
344 }
345
346 /**
347 * Given a format string and a {@link java.util.Date} object, returns a CharSequence containing
348 * the requested date.
349 * @param inFormat the format string, as described in {@link android.text.format.DateFormat}
350 * @param inDate the date to format
351 * @return a {@link CharSequence} containing the requested text
352 */
Romain Guy3d1728c2012-10-31 20:31:58 -0700353 public static CharSequence format(CharSequence inFormat, Date inDate) {
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800354 Calendar c = new GregorianCalendar();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800355 c.setTime(inDate);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800356 return format(inFormat, c);
357 }
358
359 /**
Romain Guy3d1728c2012-10-31 20:31:58 -0700360 * Indicates whether the specified format string contains seconds.
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800361 *
Romain Guy3d1728c2012-10-31 20:31:58 -0700362 * Always returns false if the input format is null.
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800363 *
Romain Guy3d1728c2012-10-31 20:31:58 -0700364 * @param inFormat the format string, as described in {@link android.text.format.DateFormat}
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800365 *
Romain Guy3d1728c2012-10-31 20:31:58 -0700366 * @return true if the format string contains {@link #SECONDS}, false otherwise
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800367 *
Romain Guy3d1728c2012-10-31 20:31:58 -0700368 * @hide
369 */
370 public static boolean hasSeconds(CharSequence inFormat) {
Jeff Sharkey06c5f8a2012-12-04 09:53:44 -0800371 return hasDesignator(inFormat, SECONDS);
372 }
373
374 /**
375 * Test if a format string contains the given designator. Always returns
376 * {@code false} if the input format is {@code null}.
377 *
378 * @hide
379 */
380 public static boolean hasDesignator(CharSequence inFormat, char designator) {
Romain Guy3d1728c2012-10-31 20:31:58 -0700381 if (inFormat == null) return false;
382
383 final int length = inFormat.length();
384
385 int c;
386 int count;
387
388 for (int i = 0; i < length; i += count) {
389 count = 1;
390 c = inFormat.charAt(i);
391
392 if (c == QUOTE) {
393 count = skipQuotedText(inFormat, i, length);
Jeff Sharkey06c5f8a2012-12-04 09:53:44 -0800394 } else if (c == designator) {
Romain Guy3d1728c2012-10-31 20:31:58 -0700395 return true;
396 }
397 }
398
399 return false;
400 }
401
402 private static int skipQuotedText(CharSequence s, int i, int len) {
403 if (i + 1 < len && s.charAt(i + 1) == QUOTE) {
404 return 2;
405 }
406
407 int count = 1;
408 // skip leading quote
409 i++;
410
411 while (i < len) {
412 char c = s.charAt(i);
413
414 if (c == QUOTE) {
415 count++;
416 // QUOTEQUOTE -> QUOTE
417 if (i + 1 < len && s.charAt(i + 1) == QUOTE) {
418 i++;
419 } else {
420 break;
421 }
422 } else {
423 i++;
424 count++;
425 }
426 }
427
428 return count;
429 }
430
431 /**
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800432 * Given a format string and a {@link java.util.Calendar} object, returns a CharSequence
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800433 * containing the requested date.
434 * @param inFormat the format string, as described in {@link android.text.format.DateFormat}
435 * @param inDate the date to format
436 * @return a {@link CharSequence} containing the requested text
437 */
Romain Guy3d1728c2012-10-31 20:31:58 -0700438 public static CharSequence format(CharSequence inFormat, Calendar inDate) {
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800439 SpannableStringBuilder s = new SpannableStringBuilder(inFormat);
440 int count;
441
442 LocaleData localeData = LocaleData.get(Locale.getDefault());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800443
444 int len = inFormat.length();
445
446 for (int i = 0; i < len; i += count) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800447 count = 1;
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800448 int c = s.charAt(i);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800449
450 if (c == QUOTE) {
451 count = appendQuotedText(s, i, len);
452 len = s.length();
453 continue;
454 }
455
456 while ((i + count < len) && (s.charAt(i + count) == c)) {
457 count++;
458 }
459
460 String replacement;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800461 switch (c) {
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800462 case 'A':
463 case 'a':
464 replacement = localeData.amPm[inDate.get(Calendar.AM_PM) - Calendar.AM];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800465 break;
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800466 case 'd':
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800467 replacement = zeroPad(inDate.get(Calendar.DATE), count);
468 break;
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800469 case 'c':
470 case 'E':
471 replacement = getDayOfWeekString(localeData,
472 inDate.get(Calendar.DAY_OF_WEEK), count, c);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800473 break;
Elliott Hughes7a89f622013-03-11 14:49:44 -0700474 case 'K': // hour in am/pm (0-11)
475 case 'h': // hour in am/pm (1-12)
476 {
477 int hour = inDate.get(Calendar.HOUR);
478 if (c == 'h' && hour == 0) {
479 hour = 12;
480 }
481 replacement = zeroPad(hour, count);
482 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800483 break;
Elliott Hughes7a89f622013-03-11 14:49:44 -0700484 case 'H': // hour in day (0-23)
Elliott Hughesfc55c2b2013-03-18 14:59:59 -0700485 case 'k': // hour in day (1-24) [but see note below]
Elliott Hughes7a89f622013-03-11 14:49:44 -0700486 {
487 int hour = inDate.get(Calendar.HOUR_OF_DAY);
Elliott Hughesfc55c2b2013-03-18 14:59:59 -0700488 // Historically on Android 'k' was interpreted as 'H', which wasn't
489 // implemented, so pretty much all callers that want to format 24-hour
490 // times are abusing 'k'. http://b/8359981.
491 if (false && c == 'k' && hour == 0) {
Elliott Hughes7a89f622013-03-11 14:49:44 -0700492 hour = 24;
493 }
494 replacement = zeroPad(hour, count);
495 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800496 break;
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800497 case 'L':
498 case 'M':
499 replacement = getMonthString(localeData,
500 inDate.get(Calendar.MONTH), count, c);
501 break;
502 case 'm':
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800503 replacement = zeroPad(inDate.get(Calendar.MINUTE), count);
504 break;
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800505 case 's':
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800506 replacement = zeroPad(inDate.get(Calendar.SECOND), count);
507 break;
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800508 case 'y':
509 replacement = getYearString(inDate.get(Calendar.YEAR), count);
510 break;
511 case 'z':
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800512 replacement = getTimeZoneString(inDate, count);
513 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800514 default:
515 replacement = null;
516 break;
517 }
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800518
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800519 if (replacement != null) {
520 s.replace(i, i + count, replacement);
521 count = replacement.length(); // CARE: count is used in the for loop above
522 len = s.length();
523 }
524 }
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800525
526 if (inFormat instanceof Spanned) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800527 return new SpannedString(s);
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800528 } else {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800529 return s.toString();
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800530 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800531 }
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800532
533 private static String getDayOfWeekString(LocaleData ld, int day, int count, int kind) {
534 boolean standalone = (kind == 'c');
535 if (count == 5) {
536 return standalone ? ld.tinyStandAloneWeekdayNames[day] : ld.tinyWeekdayNames[day];
537 } else if (count == 4) {
538 return standalone ? ld.longStandAloneWeekdayNames[day] : ld.longWeekdayNames[day];
539 } else {
540 return standalone ? ld.shortStandAloneWeekdayNames[day] : ld.shortWeekdayNames[day];
541 }
542 }
543
544 private static String getMonthString(LocaleData ld, int month, int count, int kind) {
545 boolean standalone = (kind == 'L');
546 if (count == 5) {
547 return standalone ? ld.tinyStandAloneMonthNames[month] : ld.tinyMonthNames[month];
548 } else if (count == 4) {
549 return standalone ? ld.longStandAloneMonthNames[month] : ld.longMonthNames[month];
Elliott Hughes34de3bc2012-09-14 21:10:05 -0700550 } else if (count == 3) {
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800551 return standalone ? ld.shortStandAloneMonthNames[month] : ld.shortMonthNames[month];
Elliott Hughes34de3bc2012-09-14 21:10:05 -0700552 } else {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800553 // Calendar.JANUARY == 0, so add 1 to month.
554 return zeroPad(month+1, count);
555 }
556 }
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800557
Romain Guy3d1728c2012-10-31 20:31:58 -0700558 private static String getTimeZoneString(Calendar inDate, int count) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800559 TimeZone tz = inDate.getTimeZone();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800560 if (count < 2) { // FIXME: shouldn't this be <= 2 ?
561 return formatZoneOffset(inDate.get(Calendar.DST_OFFSET) +
562 inDate.get(Calendar.ZONE_OFFSET),
563 count);
564 } else {
565 boolean dst = inDate.get(Calendar.DST_OFFSET) != 0;
566 return tz.getDisplayName(dst, TimeZone.SHORT);
567 }
568 }
569
Romain Guy3d1728c2012-10-31 20:31:58 -0700570 private static String formatZoneOffset(int offset, int count) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800571 offset /= 1000; // milliseconds to seconds
572 StringBuilder tb = new StringBuilder();
573
574 if (offset < 0) {
575 tb.insert(0, "-");
576 offset = -offset;
577 } else {
578 tb.insert(0, "+");
579 }
580
581 int hours = offset / 3600;
582 int minutes = (offset % 3600) / 60;
583
584 tb.append(zeroPad(hours, 2));
585 tb.append(zeroPad(minutes, 2));
586 return tb.toString();
587 }
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800588
589 private static String getYearString(int year, int count) {
Elliott Hughes34de3bc2012-09-14 21:10:05 -0700590 return (count <= 2) ? zeroPad(year % 100, 2)
591 : String.format(Locale.getDefault(), "%d", year);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800592 }
Elliott Hughes8326b9a2013-03-08 15:06:14 -0800593
Romain Guy3d1728c2012-10-31 20:31:58 -0700594 private static int appendQuotedText(SpannableStringBuilder s, int i, int len) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800595 if (i + 1 < len && s.charAt(i + 1) == QUOTE) {
596 s.delete(i, i + 1);
597 return 1;
598 }
599
600 int count = 0;
601
602 // delete leading quote
603 s.delete(i, i + 1);
604 len--;
605
606 while (i < len) {
607 char c = s.charAt(i);
608
609 if (c == QUOTE) {
610 // QUOTEQUOTE -> QUOTE
611 if (i + 1 < len && s.charAt(i + 1) == QUOTE) {
612
613 s.delete(i, i + 1);
614 len--;
615 count++;
616 i++;
617 } else {
618 // Closing QUOTE ends quoted text copying
619 s.delete(i, i + 1);
620 break;
621 }
622 } else {
623 i++;
624 count++;
625 }
626 }
627
628 return count;
629 }
630
Romain Guy3d1728c2012-10-31 20:31:58 -0700631 private static String zeroPad(int inValue, int inMinDigits) {
Elliott Hughes34de3bc2012-09-14 21:10:05 -0700632 return String.format(Locale.getDefault(), "%0" + inMinDigits + "d", inValue);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800633 }
634}