blob: 9a89b66422229f3e878b7a7e7b4405adddc5a7b4 [file] [log] [blame]
/*
* Copyright 2006 The Apache Software Foundation. Licensed under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with the License. You may obtain
* a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable
* law or agreed to in writing, software distributed under the License is distributed on an "AS IS"
* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*/
package org.apache.tapestry.util;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.Properties;
import java.util.TimeZone;
/**
* Converts dates to strings using the same format specifiers as strftime Note: This does not mimic
* strftime perfectly. Certain strftime commands, are not supported, and will convert as if they
* were literals. Certain complicated commands, like those dealing with the week of the year
* probably don't have exactly the same behavior as strftime. These limitations are due to use
* SimpleDateTime. If the conversion was done manually, all these limitations could be eliminated.
* The interface looks like a subset of DateFormat. Maybe someday someone will make this class
* extend DateFormat.
*
* <p>
* Added to tapestry in order to help with dojo/javascript date/time conversions.
* </p>
*
* @see "http://www.opengroup.org/onlinepubs/007908799/xsh/strftime.html"
* @author Bip Thelin
* @author Dan Sandberg
*/
public class Strftime
{
protected static Properties translate;
protected static Properties pTranslate;
protected SimpleDateFormat simpleDateFormat;
/**
* Initialize our pattern translation
*/
static {
translate = new Properties();
translate.put("a", "EEE");
translate.put("A", "EEEE");
translate.put("b", "MMM");
translate.put("B", "MMMM");
translate.put("c", "EEE MMM d HH:mm:ss yyyy");
// There's no way to specify the century in SimpleDateFormat. We don't want to hard-code
// 20 since this could be wrong for the pre-2000 files.
// translate.put("C", "20");
translate.put("d", "dd");
translate.put("D", "MM/dd/yy");
translate.put("e", "dd"); // will show as '03' instead of ' 3'
translate.put("F", "yyyy-MM-dd");
translate.put("g", "yy");
translate.put("G", "yyyy");
translate.put("H", "HH");
translate.put("h", "MMM");
translate.put("I", "hh");
translate.put("j", "DDD");
translate.put("k", "HH"); // will show as '07' instead of ' 7'
translate.put("l", "hh"); // will show as '07' instead of ' 7'
translate.put("m", "MM");
translate.put("M", "mm");
translate.put("n", "\n");
translate.put("p", "a");
translate.put("P", "a"); // will show as pm instead of PM
translate.put("r", "hh:mm:ss a");
translate.put("R", "HH:mm");
// There's no way to specify this with SimpleDateFormat
// translate.put("s","seconds since ecpoch");
translate.put("S", "ss");
translate.put("t", "\t");
translate.put("T", "HH:mm:ss");
// There's no way to specify this with SimpleDateFormat
// translate.put("u","day of week ( 1-7 )");
// There's no way to specify this with SimpleDateFormat
// translate.put("U","week in year with first sunday as first day...");
translate.put("V", "ww"); // I'm not sure this is always exactly the same
// There's no way to specify this with SimpleDateFormat
// translate.put("W","week in year with first monday as first day...");
// There's no way to specify this with SimpleDateFormat
// translate.put("w","E");
translate.put("X", "HH:mm:ss");
translate.put("x", "MM/dd/yy");
translate.put("y", "yy");
translate.put("Y", "yyyy");
translate.put("Z", "z");
translate.put("z", "Z");
translate.put("%", "%");
pTranslate = new Properties();
pTranslate.put("EEE", "%a");
pTranslate.put("EEEE", "%A");
pTranslate.put("MMM", "%b");
pTranslate.put("MMMM", "%B");
pTranslate.put("EEE MMM d HH:mm:ss yyyy", "%c");
// There's no way to specify the century in SimpleDateFormat. We don't want to hard-code
// 20 since this could be wrong for the pre-2000 files.
// translate.put("C", "20");
pTranslate.put("dd", "%d");
pTranslate.put("MM/dd/yy", "%D");
pTranslate.put("yyyy-MM-dd", "%F");
pTranslate.put("yy", "%g");
pTranslate.put("yyyy", "%G");
pTranslate.put("HH", "%H");
pTranslate.put("MMM", "%h");
pTranslate.put("hh", "%I");
pTranslate.put("DDD", "%j");
pTranslate.put("MM", "%m");
pTranslate.put("mm", "%M");
pTranslate.put("\n", "%n");
pTranslate.put("a", "%p"); // will show as pm instead of PM
pTranslate.put("hh:mm:ss a", "%r");
pTranslate.put("HH:mm", "%R");
// There's no way to specify this with SimpleDateFormat
// translate.put("s","seconds since ecpoch");
pTranslate.put("ss", "%S");
pTranslate.put("\t", "%t");
pTranslate.put("HH:mm:ss", "%T");
// There's no way to specify this with SimpleDateFormat
// translate.put("u","day of week ( 1-7 )");
// There's no way to specify this with SimpleDateFormat
// translate.put("U","week in year with first sunday as first day...");
pTranslate.put("ww", "%V"); // I'm not sure this is always exactly the same
// There's no way to specify this with SimpleDateFormat
// translate.put("W","week in year with first monday as first day...");
// There's no way to specify this with SimpleDateFormat
// translate.put("w","E");
pTranslate.put("HH:mm:ss", "%X");
pTranslate.put("MM/dd/yy", "%x");
pTranslate.put("yy", "%y");
pTranslate.put("yyyy", "%Y");
pTranslate.put("z", "%Z");
pTranslate.put("Z", "%z");
pTranslate.put("%", "%");
}
/**
* Create an instance of this date formatting class.
*
* @see #Strftime( String, Locale )
*/
public Strftime(String origFormat)
{
String convertedFormat = convertDateFormat(origFormat);
simpleDateFormat = new SimpleDateFormat(convertedFormat);
}
/**
* Create an instance of this date formatting class.
*
* @param origFormat
* the strftime-style formatting string
* @param locale
* the locale to use for locale-specific conversions
*/
public Strftime(String origFormat, Locale locale)
{
String convertedFormat = convertDateFormat(origFormat);
simpleDateFormat = new SimpleDateFormat(convertedFormat, locale);
}
/**
* Format the date according to the strftime-style string given in the constructor.
*
* @param date
* the date to format
* @return the formatted date
*/
public String format(Date date)
{
return simpleDateFormat.format(date);
}
/**
* Parses the input.
*
* @see java.text.SimpleDateFormat#parse(String)
* @param input The string to parse.
* @return A parsed {@link Date}.
* @throws ParseException On input error.
*/
public Date parse(String input)
throws ParseException
{
return simpleDateFormat.parse(input);
}
/**
* Get the timezone used for formatting conversions.
*
* @return the timezone
*/
public TimeZone getTimeZone()
{
return simpleDateFormat.getTimeZone();
}
/**
* Change the timezone used to format dates.
*
* @see SimpleDateFormat#setTimeZone(TimeZone)
*/
public void setTimeZone(TimeZone timeZone)
{
simpleDateFormat.setTimeZone(timeZone);
}
/**
* Does the exact opposite of {{@link #convertDateFormat(String)} by converting
* the incoming java date format string into a POSIX compliant format string.
* @param pattern The java date format style format
* @return The converted format into something usable by POSIX strftime style parser/formatters.
*/
public static String convertToPosixFormat(String pattern)
{
if (pattern == null) return null;
StringBuffer buf = new StringBuffer();
int start=-1;
for(int i = 0; i < pattern.length(); i++) {
char c = pattern.charAt(i);
// if in a definition
if (Character.isLetter(c)) {
if (start <= -1) start = i;
continue;
} else if (start >= 0) {
// we've hit the end of a definition
String conv = pattern.substring(start, i);
String match = pTranslate.getProperty(conv);
if (match == null)
buf.append(conv); // just append it, this shouldn't happen we hope
else
buf.append(match);
// reset
start=-1;
}
buf.append(c);
}
// grab last one, if any
if (start > -1) {
String conv = pattern.substring(start, pattern.length());
String match = pTranslate.getProperty(conv);
if (match == null) buf.append(conv);
else buf.append(match);
}
return buf.toString();
}
/**
* Search the provided pattern and get the C standard Date/Time formatting rules and convert
* them to the Java equivalent.
*
* @param pattern
* The pattern to search
* @return The modified pattern
*/
public static String convertDateFormat(String pattern)
{
boolean inside = false;
boolean mark = false;
boolean modifiedCommand = false;
StringBuffer buf = new StringBuffer();
for(int i = 0; i < pattern.length(); i++) {
char c = pattern.charAt(i);
if (c == '%' && !mark) {
mark = true;
} else {
if (mark) {
if (modifiedCommand) {
// don't do anything--we just wanted to skip a char
modifiedCommand = false;
mark = false;
} else {
inside = translateCommand(buf, pattern, i, inside);
// It's a modifier code
if (c == 'O' || c == 'E') {
modifiedCommand = true;
} else {
mark = false;
}
}
} else {
if (!inside && c != ' ') {
// We start a literal, which we need to quote
buf.append("'");
inside = true;
}
buf.append(c);
}
}
}
if (buf.length() > 0) {
char lastChar = buf.charAt(buf.length() - 1);
if (lastChar != '\'' && inside) {
buf.append('\'');
}
}
return buf.toString();
}
private static String quote(String str, boolean insideQuotes)
{
String retVal = str;
if (!insideQuotes) {
retVal = '\'' + retVal + '\'';
}
return retVal;
}
/**
* try to get the Java Date/Time formating associated with the C standard provided.
*
* @param c
* The C equivalent to translate
* @return The Java formatting rule to use
*/
private static boolean translateCommand(StringBuffer buf, String pattern, int index,
boolean oldInside)
{
char firstChar = pattern.charAt(index);
boolean newInside = oldInside;
// O and E are modifiers, they mean to present an alternative representation of the next
// char
// we just handle the next char as if the O or E wasn't there
if (firstChar == 'O' || firstChar == 'E') {
if (index + 1 < pattern.length()) {
newInside = translateCommand(buf, pattern, index + 1, oldInside);
} else {
buf.append(quote("%" + firstChar, oldInside));
}
} else {
String command = translate.getProperty(String.valueOf(firstChar));
// If we don't find a format, treat it as a literal--That's what apache does
if (command == null) {
buf.append(quote("%" + firstChar, oldInside));
} else {
// If we were inside quotes, close the quotes
if (oldInside) {
buf.append('\'');
}
buf.append(command);
newInside = false;
}
}
return newInside;
}
}