package org.robolectric.util; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; import java.util.TimeZone; /** * An implementation of the Unix strftime with some glibc extensions. */ public class Strftime { /** * Format a date string. * * @param format The format in strftime syntax. * @param date The date to format. * @param locale The locale to use for formatting. * @param zone The timezone to use for formatting. * @return The formatted datetime. */ public static String format(String format, final Date date, Locale locale, TimeZone zone) { StringBuffer buffer = new StringBuffer(); class Formatter { SimpleDateFormat formatter; public Formatter( Date date, Locale locale, TimeZone timeZone) { if (locale != null) { formatter = new SimpleDateFormat("", locale); } else { formatter = new SimpleDateFormat(""); } if (timeZone != null) { formatter.setTimeZone(timeZone); } } public String format(String format) { formatter.applyPattern(format); return formatter.format(date); } } Formatter formatter = new Formatter(date, locale, zone); Boolean inside = false; Boolean removePad = false; Boolean zeroPad = false; Boolean spacePad = false; Boolean upperCase = false; Boolean swapCase = false; StringBuffer padWidthBuffer = new StringBuffer(); for (int i = 0; i < format.length(); i++) { Character c = format.charAt(i); if (!inside && c == '%') { inside = true; removePad = false; zeroPad = false; spacePad = false; upperCase = false; swapCase = false; padWidthBuffer = new StringBuffer(); } else if(inside) { inside = false; switch (c) { // %a Abbreviated weekday name according to locale. case 'a': buffer.append( correctCase( formatter.format("EEE"), upperCase, swapCase)); break; // %A Full weekday name according to locale. case 'A': buffer.append( correctCase( formatter.format("EEEE"), upperCase, swapCase)); break; // %b Abbreviated month name according to locale. case 'b': buffer.append( correctCase( formatter.format("MMM"), upperCase, swapCase)); break; // %B Full month name according to locale. case 'B': buffer.append( correctCase( formatter.format("MMMM"), upperCase, swapCase)); break; // %c Preferred date and time representation for locale. case 'c': // NOTE: en_US locale buffer.append( formatter.format("EEE dd MMM yyyy hh:mm:ss aa z")); break; // %C Year divided by 100 and truncated to integer (00-99). case 'C': buffer.append( formatter.format("y").substring(0, 2)); break; // %d Day of the month as decimal number (01-31). case 'd': buffer.append( formatter.format("dd")); break; // %D Same as "%m/%d/%y" case 'D': buffer.append( formatter.format("MM/dd/yy")); break; // %e Day of the month as decimal number, padded with space. case 'e': buffer.append( correctPad( formatter.format("dd"), zeroPad, true, removePad, (padWidthBuffer.length() <= 0 ? new StringBuffer("2") : padWidthBuffer))); break; // %E Modifier, use a locale-dependent alternative representation. case 'E': inside = true; throw new UnsupportedOperationException("Not implemented yet"); // break; // %F ISO 8601 date format: "%Y-%m-%d". case 'F': buffer.append( formatter.format("yyyy-MM-dd")); break; // %g 2-digit year version of %G, (00-99) case 'g': buffer.append( formatter.format("YY")); break; // %G ISO 8601 week-based year. case 'G': buffer.append( formatter.format("YYYY")); break; // %h Like %b. case 'h': buffer.append( formatter.format("MMM")); break; // %H Hour (24-hour clock) as decimal number (00-23). case 'H': buffer.append( formatter.format("HH")); break; // %I Hour (12-hour clock) as decimal number (01-12). case 'I': buffer.append( formatter.format("hh")); break; // %j Day of the year as decimal number (001-366). case 'j': buffer.append( formatter.format("DDD")); break; // %k Hour (24-hour clock) as decimal number (0-23), space padded. case 'k': buffer.append( correctPad( formatter.format("HH"), zeroPad, spacePad, removePad, (padWidthBuffer.length() <= 0 ? new StringBuffer("2") : padWidthBuffer))); break; // %l Hour (12-hour clock) as decimal number (1-12), space padded. case 'l': buffer.append( correctPad( formatter.format("hh"), zeroPad, spacePad || !zeroPad, removePad, (padWidthBuffer.length() <= 0 ? new StringBuffer("2") : padWidthBuffer))); break; // %m Month as decimal number (01-12). case 'm': buffer.append( correctPad( formatter.format("MM"), zeroPad, spacePad, removePad, (padWidthBuffer.length() <= 0 ? new StringBuffer("2") : padWidthBuffer))); break; // %M Minute as decimal number (00-59). case 'M': buffer.append( correctCase( formatter.format("mm"), upperCase, swapCase)); break; // %n Newline. case 'n': buffer.append( formatter.format("\n")); break; // %O Modifier, use alternative numeric symbols (say, Roman numerals). case 'O': inside = true; throw new UnsupportedOperationException("Not implemented yet"); // break; // %p "AM", "PM", or locale string. Noon = "PM", midnight = "AM". case 'p': buffer.append( correctCase( formatter.format("a"), upperCase, swapCase)); break; // %P "am", "pm", or locale string. Noon = "pm", midnight = "am". case 'P': buffer.append( correctCase( formatter.format("a").toLowerCase(), upperCase, swapCase)); break; // %r 12-hour clock time. case 'r': buffer.append( formatter.format("hh:mm:ss a")); break; // %R 24-hour clock time, "%H:%M". case 'R': buffer.append( formatter.format("HH:mm")); break; // %s Number of seconds since Epoch, 1970-01-01 00:00:00 +0000 (UTC). case 's': buffer.append( ((Long) (date.getTime() / 1000)).toString()); break; // %S Second as decimal number (00-60). 60 for leap seconds. case 'S': buffer.append( formatter.format("ss")); break; // %t Tab. case 't': buffer.append( formatter.format("\t")); break; // %T 24-hour time, "%H:%M:%S". case 'T': buffer.append( formatter.format("HH:mm:ss")); break; // %u The day of the week as a decimal, (1-7). Monday being 1. case 'u': buffer.append( formatter.format("u")); break; // %U week number of the current year as a decimal number, (00-53). // Starting with the first Sunday as the first day of week 01. case 'U': throw new UnsupportedOperationException("Not implemented yet"); // buffer.append( // formatter.format("ww")); // break; // %V ISO 8601 week number (00-53). // Week 1 is the first week that has at least 4 days in the new year. case 'V': buffer.append( formatter.format("ww")); break; // %w Day of the week as a decimal, (0-6). Sunday being 0. case 'w': String dayNumberOfWeek = formatter.format("u"); // (1-7) buffer.append( (dayNumberOfWeek.equals("7") ? "0" : dayNumberOfWeek)); break; // %W Week number of the current year as a decimal number, (00-53). // Starting with the first Monday as the first day of week 01. case 'W': throw new UnsupportedOperationException("Not implemented yet"); // buffer.append( // formatter.format("ww")); // break; // %x Locale date without time. case 'x': buffer.append( formatter.format("MM/dd/yyyy")); break; // %X Locale time without date. case 'X': buffer.append( formatter.format("hh:mm:ss aa")); // buffer.append( // formatter.format("HH:mm:ss")); break; // %y Year as decimal number without century (00-99). case 'y': buffer.append( formatter.format("yy")); break; // %Y Year as decimal number with century. case 'Y': buffer.append( formatter.format("yyyy")); break; // %z Numeric timezone as hour and minute offset from UTC "+hhmm" or "-hhmm". case 'z': buffer.append( formatter.format("Z")); break; // %Z Timezone, name, or abbreviation. case 'Z': buffer.append( formatter.format("z")); break; // %% Literal '%'. case '%': buffer.append( formatter.format("%")); break; // glibc extension // %^ Force upper case. case '^': inside = true; upperCase = true; break; // %# Swap case. case '#': inside = true; swapCase = true; break; // %- Remove padding. case '-': inside = true; removePad = true; break; // %_ Space pad. case '_': inside = true; spacePad = true; break; // %0 Zero pad. // 0 Alternatively if preceded by another digit, defines padding width. case '0': inside = true; if (padWidthBuffer.length() == 0) { zeroPad = true; spacePad = false; } else { padWidthBuffer.append(c); } break; // %1 Padding width. case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': inside = true; // zeroPad = !spacePad; // Default to zero padding. padWidthBuffer.append(c); break; default: buffer.append(c.toString()); break; } } else { buffer.append(c.toString()); } } return buffer.toString(); } private static String correctCase( String simple, Boolean upperCase, Boolean swapCase) { if (upperCase) { return simple.toUpperCase(); } if (!swapCase) { return simple; } // swap case StringBuffer buffer = new StringBuffer(); for (int i = 0; i < simple.length(); i++) { Character c = simple.charAt(i); buffer.append( (Character.isLowerCase(c) ? Character.toUpperCase(c) : Character.toLowerCase(c)) ); } return buffer.toString(); } private static String correctPad( String simple, Boolean zeroPad, Boolean spacePad, Boolean removePad, StringBuffer padWidthBuffer) { String unpadded = simple.replaceFirst("^(0+| +)(?!$)", ""); if (removePad) { return unpadded; } int padWidth = 0; if (padWidthBuffer.length() > 0) { padWidth = ( Integer.parseInt(padWidthBuffer.toString()) - unpadded.length()); } if (spacePad || zeroPad) { StringBuffer buffer = new StringBuffer(); char padChar = (spacePad ? ' ' : '0'); for (int i = 0 ; i < padWidth ; i++) { buffer.append(padChar); } buffer.append(unpadded); return buffer.toString(); } return simple; } }