Home | History | Annotate | Download | only in util
      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    */
     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 
    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 
    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