• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package org.robolectric.util;
2 
3 import java.text.SimpleDateFormat;
4 import java.util.Date;
5 import java.util.Locale;
6 import java.util.TimeZone;
7 
8 /**
9  * An implementation of the Unix strftime with some glibc extensions.
10  */
11 public class Strftime {
12 
13   /**
14    * Format a date string.
15    *
16    * @param format The format in strftime syntax.
17    * @param date The date to format.
18    * @param locale The locale to use for formatting.
19    * @param zone The timezone to use for formatting.
20    * @return The formatted datetime.
21    */
format(String format, final Date date, Locale locale, TimeZone zone)22   public static String format(String format, final Date date, Locale locale, TimeZone zone) {
23     StringBuilder buffer = new StringBuilder();
24 
25     class Formatter {
26       SimpleDateFormat formatter;
27 
28       public Formatter(
29           Date date,
30           Locale locale,
31           TimeZone timeZone) {
32         if (locale != null) {
33           formatter = new SimpleDateFormat("", locale);
34         } else {
35           formatter = new SimpleDateFormat("");
36         }
37         if (timeZone != null) {
38           formatter.setTimeZone(timeZone);
39         }
40       }
41 
42       public String format(String format) {
43         formatter.applyPattern(format);
44         return formatter.format(date);
45       }
46     }
47 
48     Formatter formatter = new Formatter(date, locale, zone);
49 
50     Boolean inside = false;
51 
52     Boolean removePad = false;
53     Boolean zeroPad = false;
54     Boolean spacePad = false;
55 
56     Boolean upperCase = false;
57     Boolean swapCase = false;
58 
59     StringBuilder padWidthBuffer = new StringBuilder();
60 
61     for (int i = 0; i < format.length(); i++) {
62       Character c = format.charAt(i);
63 
64       if (!inside && c == '%') {
65         inside = true;
66         removePad = false;
67         zeroPad = false;
68         spacePad = false;
69         upperCase = false;
70         swapCase = false;
71         padWidthBuffer = new StringBuilder();
72       } else if(inside) {
73         inside = false;
74         switch (c) {
75           // %a  Abbreviated weekday name according to locale.
76           case 'a':
77             buffer.append(
78                 correctCase(
79                     formatter.format("EEE"),
80                     upperCase,
81                     swapCase));
82             break;
83 
84           // %A  Full weekday name according to locale.
85           case 'A':
86             buffer.append(
87                 correctCase(
88                     formatter.format("EEEE"),
89                     upperCase,
90                     swapCase));
91             break;
92 
93           // %b  Abbreviated month name according to locale.
94           case 'b':
95             buffer.append(
96                 correctCase(
97                     formatter.format("MMM"),
98                     upperCase,
99                     swapCase));
100             break;
101 
102           // %B  Full month name according to locale.
103           case 'B':
104             buffer.append(
105                 correctCase(
106                     formatter.format("MMMM"),
107                     upperCase,
108                     swapCase));
109             break;
110 
111           // %c  Preferred date and time representation for locale.
112           case 'c':
113             // NOTE: en_US locale
114             buffer.append(
115                 formatter.format("EEE dd MMM yyyy hh:mm:ss aa z"));
116             break;
117 
118           // %C  Year divided by 100 and truncated to integer (00-99).
119           case 'C':
120             buffer.append(
121                 formatter.format("y").substring(0, 2));
122             break;
123 
124           // %d   Day of the month as decimal number (01-31).
125           case 'd':
126             buffer.append(
127                 formatter.format("dd"));
128             break;
129 
130           // %D  Same as "%m/%d/%y"
131           case 'D':
132             buffer.append(
133                 formatter.format("MM/dd/yy"));
134             break;
135 
136           // %e  Day of the month as decimal number, padded with space.
137           case 'e':
138             buffer.append(
139                 correctPad(
140                     formatter.format("dd"),
141                     zeroPad,
142                     true,
143                     removePad,
144                     (padWidthBuffer.length() <= 0
145                         ? new StringBuilder("2")
146                         : padWidthBuffer)));
147             break;
148 
149           // %E  Modifier, use a locale-dependent alternative representation.
150           case 'E':
151             inside = true;
152             throw new UnsupportedOperationException("Not implemented yet");
153 //            break;
154 
155           // %F  ISO 8601 date format: "%Y-%m-%d".
156           case 'F':
157             buffer.append(
158                 formatter.format("yyyy-MM-dd"));
159             break;
160 
161           // %g  2-digit year version of %G, (00-99)
162           case 'g':
163             buffer.append(
164                 formatter.format("YY"));
165             break;
166 
167           // %G  ISO 8601 week-based year.
168           case 'G':
169             buffer.append(
170                 formatter.format("YYYY"));
171             break;
172 
173           // %h  Like %b.
174           case 'h':
175             buffer.append(
176                 formatter.format("MMM"));
177             break;
178 
179           // %H  Hour (24-hour clock) as decimal number (00-23).
180           case 'H':
181             buffer.append(
182                 formatter.format("HH"));
183             break;
184 
185           // %I  Hour (12-hour clock) as decimal number (01-12).
186           case 'I':
187             buffer.append(
188                 formatter.format("hh"));
189             break;
190 
191           // %j  Day of the year as decimal number (001-366).
192           case 'j':
193             buffer.append(
194                 formatter.format("DDD"));
195             break;
196 
197           // %k  Hour (24-hour clock) as decimal number (0-23), space padded.
198           case 'k':
199             buffer.append(
200                 correctPad(
201                     formatter.format("HH"),
202                     zeroPad,
203                     spacePad,
204                     removePad,
205                     (padWidthBuffer.length() <= 0
206                         ? new StringBuilder("2")
207                         : padWidthBuffer)));
208             break;
209 
210           // %l  Hour (12-hour clock) as decimal number (1-12), space padded.
211           case 'l':
212             buffer.append(
213                 correctPad(
214                     formatter.format("hh"),
215                     zeroPad,
216                     spacePad || !zeroPad,
217                     removePad,
218                     (padWidthBuffer.length() <= 0
219                         ? new StringBuilder("2")
220                         : padWidthBuffer)));
221             break;
222 
223           // %m  Month as decimal number (01-12).
224           case 'm':
225             buffer.append(
226                 correctPad(
227                     formatter.format("MM"),
228                     zeroPad,
229                     spacePad,
230                     removePad,
231                     (padWidthBuffer.length() <= 0
232                         ? new StringBuilder("2")
233                         : padWidthBuffer)));
234             break;
235 
236           // %M  Minute as decimal number (00-59).
237           case 'M':
238             buffer.append(
239                 correctCase(
240                     formatter.format("mm"),
241                     upperCase,
242                     swapCase));
243             break;
244 
245           // %n  Newline.
246           case 'n':
247             buffer.append(
248                 formatter.format("\n"));
249             break;
250 
251           // %O  Modifier, use alternative numeric symbols (say, Roman numerals).
252           case 'O':
253             inside = true;
254             throw new UnsupportedOperationException("Not implemented yet");
255 //            break;
256 
257           // %p  "AM", "PM", or locale string. Noon = "PM", midnight = "AM".
258           case 'p':
259             buffer.append(
260                 correctCase(
261                     formatter.format("a"),
262                     upperCase,
263                     swapCase));
264             break;
265 
266           // %P  "am", "pm", or locale string. Noon = "pm", midnight = "am".
267           case 'P':
268             buffer.append(
269                 correctCase(
270                     formatter.format("a").toLowerCase(),
271                     upperCase,
272                     swapCase));
273             break;
274 
275           // %r  12-hour clock time.
276           case 'r':
277             buffer.append(
278                 formatter.format("hh:mm:ss a"));
279             break;
280 
281           // %R  24-hour clock time, "%H:%M".
282           case 'R':
283             buffer.append(
284                 formatter.format("HH:mm"));
285             break;
286 
287           // %s  Number of seconds since Epoch, 1970-01-01 00:00:00 +0000 (UTC).
288           case 's':
289             buffer.append(
290                 ((Long) (date.getTime() / 1000)).toString());
291             break;
292 
293           // %S  Second as decimal number (00-60). 60 for leap seconds.
294           case 'S':
295             buffer.append(
296                 formatter.format("ss"));
297             break;
298 
299           // %t  Tab.
300           case 't':
301             buffer.append(
302                 formatter.format("\t"));
303             break;
304 
305           // %T  24-hour time, "%H:%M:%S".
306           case 'T':
307             buffer.append(
308                 formatter.format("HH:mm:ss"));
309             break;
310 
311           // %u  The day of the week as a decimal, (1-7). Monday being 1.
312           case 'u':
313             buffer.append(
314                 formatter.format("u"));
315             break;
316 
317           // %U  week number of the current year as a decimal number, (00-53).
318           // Starting with the first Sunday as the first day of week 01.
319           case 'U':
320             throw new UnsupportedOperationException("Not implemented yet");
321             // buffer.append(
322             //     formatter.format("ww"));
323             // break;
324 
325           // %V  ISO 8601 week number (00-53).
326           // Week 1 is the first week that has at least 4 days in the new year.
327           case 'V':
328             buffer.append(
329                 formatter.format("ww"));
330             break;
331 
332           // %w  Day of the week as a decimal, (0-6). Sunday being 0.
333           case 'w':
334             String dayNumberOfWeek = formatter.format("u"); // (1-7)
335             buffer.append(
336                 (dayNumberOfWeek.equals("7") ? "0" : dayNumberOfWeek));
337             break;
338 
339           // %W  Week number of the current year as a decimal number, (00-53).
340           // Starting with the first Monday as the first day of week 01.
341           case 'W':
342             throw new UnsupportedOperationException("Not implemented yet");
343             // buffer.append(
344             //     formatter.format("ww"));
345             // break;
346 
347           // %x  Locale date without time.
348           case 'x':
349             buffer.append(
350                 formatter.format("MM/dd/yyyy"));
351             break;
352 
353           // %X  Locale time without date.
354           case 'X':
355             buffer.append(
356                 formatter.format("hh:mm:ss aa"));
357             // buffer.append(
358             //     formatter.format("HH:mm:ss"));
359             break;
360 
361           // %y  Year as decimal number without century (00-99).
362           case 'y':
363             buffer.append(
364                 formatter.format("yy"));
365             break;
366 
367           // %Y  Year as decimal number with century.
368           case 'Y':
369             buffer.append(
370                 formatter.format("yyyy"));
371             break;
372 
373           // %z  Numeric timezone as hour and minute offset from UTC "+hhmm" or "-hhmm".
374           case 'z':
375             buffer.append(
376                 formatter.format("Z"));
377             break;
378 
379           // %Z  Timezone, name, or abbreviation.
380           case 'Z':
381             buffer.append(
382                 formatter.format("z"));
383             break;
384 
385           // %%  Literal '%'.
386           case '%':
387             buffer.append(
388                 formatter.format("%"));
389             break;
390 
391           // glibc extension
392 
393           // %^  Force upper case.
394           case '^':
395             inside = true;
396             upperCase = true;
397             break;
398 
399           // %#  Swap case.
400           case '#':
401             inside = true;
402             swapCase = true;
403             break;
404 
405           // %-  Remove padding.
406           case '-':
407             inside = true;
408             removePad = true;
409             break;
410 
411           // %_  Space pad.
412           case '_':
413             inside = true;
414             spacePad = true;
415             break;
416 
417           // %0  Zero pad.
418           //  0  Alternatively if preceded by another digit, defines padding width.
419           case '0':
420             inside = true;
421             if (padWidthBuffer.length() == 0) {
422               zeroPad = true;
423               spacePad = false;
424             } else {
425               padWidthBuffer.append(c);
426             }
427             break;
428 
429           // %1  Padding width.
430           case '1':
431           case '2':
432           case '3':
433           case '4':
434           case '5':
435           case '6':
436           case '7':
437           case '8':
438           case '9':
439             inside = true;
440             // zeroPad = !spacePad; // Default to zero padding.
441             padWidthBuffer.append(c);
442             break;
443 
444           default:
445             buffer.append(c.toString());
446             break;
447         }
448       } else {
449         buffer.append(c.toString());
450       }
451     }
452 
453     return buffer.toString();
454   }
455 
correctCase( String simple, Boolean upperCase, Boolean swapCase)456   private static String correctCase(
457       String simple,
458       Boolean upperCase,
459       Boolean swapCase) {
460     if (upperCase) {
461       return simple.toUpperCase();
462     }
463 
464     if (!swapCase) {
465       return simple;
466     }
467 
468     // swap case
469     StringBuilder buffer = new StringBuilder();
470     for (int i = 0; i < simple.length(); i++) {
471       Character c = simple.charAt(i);
472       buffer.append(
473           (Character.isLowerCase(c)
474               ? Character.toUpperCase(c)
475               : Character.toLowerCase(c))
476           );
477     }
478 
479     return buffer.toString();
480   }
481 
correctPad( String simple, Boolean zeroPad, Boolean spacePad, Boolean removePad, StringBuilder padWidthBuffer)482   private static String correctPad(
483       String simple,
484       Boolean zeroPad,
485       Boolean spacePad,
486       Boolean removePad,
487       StringBuilder padWidthBuffer) {
488     String unpadded = simple.replaceFirst("^(0+| +)(?!$)", "");
489 
490     if (removePad) {
491       return unpadded;
492     }
493 
494     int padWidth = 0;
495     if (padWidthBuffer.length() > 0) {
496       padWidth = (
497           Integer.parseInt(padWidthBuffer.toString()) - unpadded.length());
498     }
499 
500     if (spacePad || zeroPad) {
501       StringBuilder buffer = new StringBuilder();
502       char padChar = (spacePad ? ' ' : '0');
503       for (int i = 0 ; i < padWidth ; i++) {
504         buffer.append(padChar);
505       }
506       buffer.append(unpadded);
507       return buffer.toString();
508     }
509 
510     return simple;
511   }
512 }
513