SimpleDateFormat.java [plain text]
package java.text;
import gnu.java.text.AttributedFormatBuffer;
import gnu.java.text.FormatBuffer;
import gnu.java.text.FormatCharacterIterator;
import gnu.java.text.StringFormatBuffer;
import java.io.IOException;
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Iterator;
import java.util.Locale;
import java.util.TimeZone;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class SimpleDateFormat extends DateFormat
{
private class CompiledField
{
private int field;
private int size;
private char character;
public CompiledField(int f, int s, char c)
{
field = f;
size = s;
character = c;
}
public int getField()
{
return field;
}
public int getSize()
{
return size;
}
public char getCharacter()
{
return character;
}
public String toString()
{
StringBuffer builder;
builder = new StringBuffer(getClass().getName());
builder.append("[field=");
builder.append(field);
builder.append(", size=");
builder.append(size);
builder.append(", character=");
builder.append(character);
builder.append("]");
return builder.toString();
}
}
private transient ArrayList tokens;
private DateFormatSymbols formatData;
private Date defaultCenturyStart;
private transient int defaultCentury;
private String pattern;
private int serialVersionOnStream = 1;
private static final long serialVersionUID = 4774881970558875024L;
private static final String standardChars = "GyMdkHmsSEDFwWahKzYeugAZ";
private void readObject(ObjectInputStream stream)
throws IOException, ClassNotFoundException
{
stream.defaultReadObject();
if (serialVersionOnStream < 1)
{
computeCenturyStart ();
serialVersionOnStream = 1;
}
else
set2DigitYearStart(defaultCenturyStart);
tokens = new ArrayList();
try
{
compileFormat(pattern);
}
catch (IllegalArgumentException e)
{
throw new InvalidObjectException("The stream pattern was invalid.");
}
}
private void compileFormat(String pattern)
{
char thisChar;
int pos;
int field;
CompiledField current = null;
for (int i=0; i<pattern.length(); i++) {
thisChar = pattern.charAt(i);
field = standardChars.indexOf(thisChar);
if (field == -1) {
current = null;
if ((thisChar >= 'A' && thisChar <= 'Z')
|| (thisChar >= 'a' && thisChar <= 'z')) {
throw new IllegalArgumentException("Invalid letter " + thisChar +
"encountered at character " + i
+ ".");
} else if (thisChar == '\'') {
pos = pattern.indexOf('\'',i+1);
if (pos == -1) {
throw new IllegalArgumentException("Quotes starting at character "
+ i + " not closed.");
}
if ((pos+1 < pattern.length()) && (pattern.charAt(pos+1) == '\'')) {
tokens.add(pattern.substring(i+1,pos+1));
} else {
tokens.add(pattern.substring(i+1,pos));
}
i = pos;
} else {
tokens.add(new Character(thisChar));
}
} else {
if ((current != null) && (field == current.field)) {
current.size++;
} else {
current = new CompiledField(field,1,thisChar);
tokens.add(current);
}
}
}
}
public String toString()
{
StringBuffer output = new StringBuffer(getClass().getName());
output.append("[tokens=");
output.append(tokens);
output.append(", formatData=");
output.append(formatData);
output.append(", defaultCenturyStart=");
output.append(defaultCenturyStart);
output.append(", defaultCentury=");
output.append(defaultCentury);
output.append(", pattern=");
output.append(pattern);
output.append(", serialVersionOnStream=");
output.append(serialVersionOnStream);
output.append(", standardChars=");
output.append(standardChars);
output.append("]");
return output.toString();
}
public SimpleDateFormat()
{
super();
Locale locale = Locale.getDefault();
calendar = new GregorianCalendar(locale);
computeCenturyStart();
tokens = new ArrayList();
formatData = new DateFormatSymbols(locale);
pattern = (formatData.dateFormats[DEFAULT] + ' '
+ formatData.timeFormats[DEFAULT]);
compileFormat(pattern);
numberFormat = NumberFormat.getInstance(locale);
numberFormat.setGroupingUsed (false);
numberFormat.setParseIntegerOnly (true);
numberFormat.setMaximumFractionDigits (0);
}
public SimpleDateFormat(String pattern)
{
this(pattern, Locale.getDefault());
}
public SimpleDateFormat(String pattern, Locale locale)
{
super();
calendar = new GregorianCalendar(locale);
computeCenturyStart();
tokens = new ArrayList();
formatData = new DateFormatSymbols(locale);
compileFormat(pattern);
this.pattern = pattern;
numberFormat = NumberFormat.getInstance(locale);
numberFormat.setGroupingUsed (false);
numberFormat.setParseIntegerOnly (true);
numberFormat.setMaximumFractionDigits (0);
}
public SimpleDateFormat(String pattern, DateFormatSymbols formatData)
{
super();
calendar = new GregorianCalendar();
computeCenturyStart ();
tokens = new ArrayList();
if (formatData == null)
throw new NullPointerException("formatData");
this.formatData = formatData;
compileFormat(pattern);
this.pattern = pattern;
numberFormat = NumberFormat.getInstance();
numberFormat.setGroupingUsed (false);
numberFormat.setParseIntegerOnly (true);
numberFormat.setMaximumFractionDigits (0);
}
public String toPattern()
{
return pattern;
}
public String toLocalizedPattern()
{
String localChars = formatData.getLocalPatternChars();
return translateLocalizedPattern(pattern, standardChars, localChars);
}
public void applyPattern(String pattern)
{
tokens = new ArrayList();
compileFormat(pattern);
this.pattern = pattern;
}
public void applyLocalizedPattern(String pattern)
{
String localChars = formatData.getLocalPatternChars();
pattern = translateLocalizedPattern(pattern, localChars, standardChars);
applyPattern(pattern);
}
private String translateLocalizedPattern(String pattern,
String oldChars, String newChars)
{
int len = pattern.length();
StringBuffer buf = new StringBuffer(len);
boolean quoted = false;
for (int i = 0; i < len; i++)
{
char ch = pattern.charAt(i);
if (ch == '\'')
quoted = ! quoted;
if (! quoted)
{
int j = oldChars.indexOf(ch);
if (j >= 0)
ch = newChars.charAt(j);
}
buf.append(ch);
}
return buf.toString();
}
public Date get2DigitYearStart()
{
return defaultCenturyStart;
}
public void set2DigitYearStart(Date date)
{
defaultCenturyStart = date;
calendar.clear();
calendar.setTime(date);
int year = calendar.get(Calendar.YEAR);
defaultCentury = year - (year % 100);
}
public DateFormatSymbols getDateFormatSymbols()
{
return (DateFormatSymbols) formatData.clone();
}
public void setDateFormatSymbols(DateFormatSymbols formatData)
{
if (formatData == null)
{
throw new
NullPointerException("The supplied format data was null.");
}
this.formatData = formatData;
}
public boolean equals(Object o)
{
if (!super.equals(o))
return false;
if (!(o instanceof SimpleDateFormat))
return false;
SimpleDateFormat sdf = (SimpleDateFormat)o;
if (defaultCentury != sdf.defaultCentury)
return false;
if (!toPattern().equals(sdf.toPattern()))
return false;
if (!getDateFormatSymbols().equals(sdf.getDateFormatSymbols()))
return false;
return true;
}
public int hashCode()
{
return super.hashCode() ^ toPattern().hashCode() ^ defaultCentury ^
getDateFormatSymbols().hashCode();
}
private void formatWithAttribute(Date date, FormatBuffer buffer, FieldPosition pos)
{
String temp;
AttributedCharacterIterator.Attribute attribute;
calendar.setTime(date);
Iterator iter = tokens.iterator();
while (iter.hasNext())
{
Object o = iter.next();
if (o instanceof CompiledField)
{
CompiledField cf = (CompiledField) o;
int beginIndex = buffer.length();
switch (cf.getField())
{
case ERA_FIELD:
buffer.append (formatData.eras[calendar.get (Calendar.ERA)], DateFormat.Field.ERA);
break;
case YEAR_FIELD:
buffer.setDefaultAttribute (DateFormat.Field.YEAR);
if (cf.getSize() == 2)
{
temp = String.valueOf (calendar.get (Calendar.YEAR));
buffer.append (temp.substring (temp.length() - 2));
}
else
withLeadingZeros (calendar.get (Calendar.YEAR), cf.getSize(), buffer);
break;
case MONTH_FIELD:
buffer.setDefaultAttribute (DateFormat.Field.MONTH);
if (cf.getSize() < 3)
withLeadingZeros (calendar.get (Calendar.MONTH) + 1, cf.getSize(), buffer);
else if (cf.getSize() < 4)
buffer.append (formatData.shortMonths[calendar.get (Calendar.MONTH)]);
else
buffer.append (formatData.months[calendar.get (Calendar.MONTH)]);
break;
case DATE_FIELD:
buffer.setDefaultAttribute (DateFormat.Field.DAY_OF_MONTH);
withLeadingZeros (calendar.get (Calendar.DATE), cf.getSize(), buffer);
break;
case HOUR_OF_DAY1_FIELD: buffer.setDefaultAttribute(DateFormat.Field.HOUR_OF_DAY1);
withLeadingZeros ( ((calendar.get (Calendar.HOUR_OF_DAY) + 23) % 24) + 1,
cf.getSize(), buffer);
break;
case HOUR_OF_DAY0_FIELD: buffer.setDefaultAttribute (DateFormat.Field.HOUR_OF_DAY0);
withLeadingZeros (calendar.get (Calendar.HOUR_OF_DAY), cf.getSize(), buffer);
break;
case MINUTE_FIELD:
buffer.setDefaultAttribute (DateFormat.Field.MINUTE);
withLeadingZeros (calendar.get (Calendar.MINUTE),
cf.getSize(), buffer);
break;
case SECOND_FIELD:
buffer.setDefaultAttribute (DateFormat.Field.SECOND);
withLeadingZeros(calendar.get (Calendar.SECOND),
cf.getSize(), buffer);
break;
case MILLISECOND_FIELD:
buffer.setDefaultAttribute (DateFormat.Field.MILLISECOND);
withLeadingZeros (calendar.get (Calendar.MILLISECOND), cf.getSize(), buffer);
break;
case DAY_OF_WEEK_FIELD:
buffer.setDefaultAttribute (DateFormat.Field.DAY_OF_WEEK);
if (cf.getSize() < 4)
buffer.append (formatData.shortWeekdays[calendar.get (Calendar.DAY_OF_WEEK)]);
else
buffer.append (formatData.weekdays[calendar.get (Calendar.DAY_OF_WEEK)]);
break;
case DAY_OF_YEAR_FIELD:
buffer.setDefaultAttribute (DateFormat.Field.DAY_OF_YEAR);
withLeadingZeros (calendar.get (Calendar.DAY_OF_YEAR), cf.getSize(), buffer);
break;
case DAY_OF_WEEK_IN_MONTH_FIELD:
buffer.setDefaultAttribute (DateFormat.Field.DAY_OF_WEEK_IN_MONTH);
withLeadingZeros (calendar.get (Calendar.DAY_OF_WEEK_IN_MONTH),
cf.getSize(), buffer);
break;
case WEEK_OF_YEAR_FIELD:
buffer.setDefaultAttribute (DateFormat.Field.WEEK_OF_YEAR);
withLeadingZeros (calendar.get (Calendar.WEEK_OF_YEAR),
cf.getSize(), buffer);
break;
case WEEK_OF_MONTH_FIELD:
buffer.setDefaultAttribute (DateFormat.Field.WEEK_OF_MONTH);
withLeadingZeros (calendar.get (Calendar.WEEK_OF_MONTH),
cf.getSize(), buffer);
break;
case AM_PM_FIELD:
buffer.setDefaultAttribute (DateFormat.Field.AM_PM);
buffer.append (formatData.ampms[calendar.get (Calendar.AM_PM)]);
break;
case HOUR1_FIELD: buffer.setDefaultAttribute (DateFormat.Field.HOUR1);
withLeadingZeros (((calendar.get (Calendar.HOUR) + 11) % 12) + 1,
cf.getSize(), buffer);
break;
case HOUR0_FIELD: buffer.setDefaultAttribute (DateFormat.Field.HOUR0);
withLeadingZeros (calendar.get (Calendar.HOUR), cf.getSize(), buffer);
break;
case TIMEZONE_FIELD:
buffer.setDefaultAttribute (DateFormat.Field.TIME_ZONE);
TimeZone zone = calendar.getTimeZone();
boolean isDST = calendar.get (Calendar.DST_OFFSET) != 0;
String zoneID = zone.getDisplayName
(isDST, cf.getSize() > 3 ? TimeZone.LONG : TimeZone.SHORT);
buffer.append (zoneID);
break;
case RFC822_TIMEZONE_FIELD:
buffer.setDefaultAttribute(DateFormat.Field.RFC822_TIME_ZONE);
int pureMinutes = (calendar.get(Calendar.ZONE_OFFSET) +
calendar.get(Calendar.DST_OFFSET)) / (1000 * 60);
String sign = (pureMinutes < 0) ? "-" : "+";
int hours = pureMinutes / 60;
int minutes = pureMinutes % 60;
buffer.append(sign);
withLeadingZeros(hours, 2, buffer);
withLeadingZeros(minutes, 2, buffer);
break;
default:
throw new IllegalArgumentException ("Illegal pattern character " +
cf.getCharacter());
}
if (pos != null && (buffer.getDefaultAttribute() == pos.getFieldAttribute()
|| cf.getField() == pos.getField()))
{
pos.setBeginIndex(beginIndex);
pos.setEndIndex(buffer.length());
}
}
else
{
buffer.append(o.toString(), null);
}
}
}
public StringBuffer format(Date date, StringBuffer buffer, FieldPosition pos)
{
formatWithAttribute(date, new StringFormatBuffer (buffer), pos);
return buffer;
}
public AttributedCharacterIterator formatToCharacterIterator(Object date)
throws IllegalArgumentException
{
if (date == null)
throw new NullPointerException("null argument");
if (!(date instanceof Date))
throw new IllegalArgumentException("argument should be an instance of java.util.Date");
AttributedFormatBuffer buf = new AttributedFormatBuffer();
formatWithAttribute((Date)date, buf,
null);
buf.sync();
return new FormatCharacterIterator(buf.getBuffer().toString(),
buf.getRanges(),
buf.getAttributes());
}
private void withLeadingZeros(int value, int length, FormatBuffer buffer)
{
String valStr = String.valueOf(value);
for (length -= valStr.length(); length > 0; length--)
buffer.append('0');
buffer.append(valStr);
}
private boolean expect(String source, ParsePosition pos, char ch)
{
int x = pos.getIndex();
boolean r = x < source.length() && source.charAt(x) == ch;
if (r)
pos.setIndex(x + 1);
else
pos.setErrorIndex(x);
return r;
}
public Date parse (String dateStr, ParsePosition pos)
{
int fmt_index = 0;
int fmt_max = pattern.length();
calendar.clear();
boolean saw_timezone = false;
int quote_start = -1;
boolean is2DigitYear = false;
try
{
for (; fmt_index < fmt_max; ++fmt_index)
{
char ch = pattern.charAt(fmt_index);
if (ch == '\'')
{
int index = pos.getIndex();
if (fmt_index < fmt_max - 1
&& pattern.charAt(fmt_index + 1) == '\'')
{
if (! expect (dateStr, pos, ch))
return null;
++fmt_index;
}
else
quote_start = quote_start < 0 ? fmt_index : -1;
continue;
}
if (quote_start != -1
|| ((ch < 'a' || ch > 'z')
&& (ch < 'A' || ch > 'Z')))
{
if (! expect (dateStr, pos, ch))
return null;
continue;
}
int fmt_count = 1;
while (++fmt_index < fmt_max && pattern.charAt(fmt_index) == ch)
{
++fmt_count;
}
boolean limit_digits = false;
if (fmt_index < fmt_max
&& standardChars.indexOf(pattern.charAt(fmt_index)) >= 0)
limit_digits = true;
--fmt_index;
int calendar_field;
boolean is_numeric = true;
int offset = 0;
boolean maybe2DigitYear = false;
boolean oneBasedHour = false;
boolean oneBasedHourOfDay = false;
Integer simpleOffset;
String[] set1 = null;
String[] set2 = null;
switch (ch)
{
case 'd':
calendar_field = Calendar.DATE;
break;
case 'D':
calendar_field = Calendar.DAY_OF_YEAR;
break;
case 'F':
calendar_field = Calendar.DAY_OF_WEEK_IN_MONTH;
break;
case 'E':
is_numeric = false;
offset = 1;
calendar_field = Calendar.DAY_OF_WEEK;
set1 = formatData.getWeekdays();
set2 = formatData.getShortWeekdays();
break;
case 'w':
calendar_field = Calendar.WEEK_OF_YEAR;
break;
case 'W':
calendar_field = Calendar.WEEK_OF_MONTH;
break;
case 'M':
calendar_field = Calendar.MONTH;
if (fmt_count <= 2)
offset = -1;
else
{
is_numeric = false;
set1 = formatData.getMonths();
set2 = formatData.getShortMonths();
}
break;
case 'y':
calendar_field = Calendar.YEAR;
if (fmt_count <= 2)
maybe2DigitYear = true;
break;
case 'K':
calendar_field = Calendar.HOUR;
break;
case 'h':
calendar_field = Calendar.HOUR;
oneBasedHour = true;
break;
case 'H':
calendar_field = Calendar.HOUR_OF_DAY;
break;
case 'k':
calendar_field = Calendar.HOUR_OF_DAY;
oneBasedHourOfDay = true;
break;
case 'm':
calendar_field = Calendar.MINUTE;
break;
case 's':
calendar_field = Calendar.SECOND;
break;
case 'S':
calendar_field = Calendar.MILLISECOND;
break;
case 'a':
is_numeric = false;
calendar_field = Calendar.AM_PM;
set1 = formatData.getAmPmStrings();
break;
case 'z':
case 'Z':
is_numeric = false;
calendar_field = Calendar.ZONE_OFFSET;
String[][] zoneStrings = formatData.getZoneStrings();
int zoneCount = zoneStrings.length;
int index = pos.getIndex();
boolean found_zone = false;
simpleOffset = computeOffset(dateStr.substring(index));
if (simpleOffset != null)
{
found_zone = true;
saw_timezone = true;
calendar.set(Calendar.DST_OFFSET, 0);
offset = simpleOffset.intValue();
}
else
{
for (int j = 0; j < zoneCount; j++)
{
String[] strings = zoneStrings[j];
int k;
for (k = 0; k < strings.length; ++k)
{
if (dateStr.startsWith(strings[k], index))
break;
}
if (k != strings.length)
{
found_zone = true;
saw_timezone = true;
TimeZone tz = TimeZone.getTimeZone (strings[0]);
if(k == 3 || k == 4)
calendar.set (Calendar.DST_OFFSET, tz.getDSTSavings());
else
calendar.set (Calendar.DST_OFFSET, 0);
offset = tz.getRawOffset ();
pos.setIndex(index + strings[k].length());
break;
}
}
}
if (! found_zone)
{
pos.setErrorIndex(pos.getIndex());
return null;
}
break;
default:
pos.setErrorIndex(pos.getIndex());
return null;
}
int value;
int index = -1;
if (is_numeric)
{
numberFormat.setMinimumIntegerDigits(fmt_count);
if (limit_digits)
numberFormat.setMaximumIntegerDigits(fmt_count);
if (maybe2DigitYear)
index = pos.getIndex();
Number n = numberFormat.parse(dateStr, pos);
if (pos == null || ! (n instanceof Long))
return null;
value = n.intValue() + offset;
}
else if (set1 != null)
{
index = pos.getIndex();
int i;
boolean found = false;
for (i = offset; i < set1.length; ++i)
{
if (set1[i] != null)
if (dateStr.toUpperCase().startsWith(set1[i].toUpperCase(),
index))
{
found = true;
pos.setIndex(index + set1[i].length());
break;
}
}
if (!found && set2 != null)
{
for (i = offset; i < set2.length; ++i)
{
if (set2[i] != null)
if (dateStr.toUpperCase().startsWith(set2[i].toUpperCase(),
index))
{
found = true;
pos.setIndex(index + set2[i].length());
break;
}
}
}
if (!found)
{
pos.setErrorIndex(index);
return null;
}
value = i;
}
else
value = offset;
if (maybe2DigitYear)
{
int digit_count = pos.getIndex() - index;
if (digit_count == 2)
{
is2DigitYear = true;
value += defaultCentury;
}
}
if (oneBasedHour && value == 12)
value = 0;
if (oneBasedHourOfDay && value == 24)
value = 0;
calendar.set(calendar_field, value);
}
if (is2DigitYear)
{
int year = calendar.get(Calendar.YEAR);
if (calendar.getTime().compareTo(defaultCenturyStart) < 0)
calendar.set(Calendar.YEAR, year + 100);
}
if (! saw_timezone)
{
calendar.clear (Calendar.DST_OFFSET);
calendar.clear (Calendar.ZONE_OFFSET);
}
return calendar.getTime();
}
catch (IllegalArgumentException x)
{
pos.setErrorIndex(pos.getIndex());
return null;
}
}
private Integer computeOffset(String zoneString)
{
Pattern pattern =
Pattern.compile("(GMT)?([+-])([012])?([0-9]):?([0-9]{2})");
Matcher matcher = pattern.matcher(zoneString);
if (matcher.matches())
{
int sign = matcher.group(2).equals("+") ? 1 : -1;
int hour = (Integer.parseInt(matcher.group(3)) * 10)
+ Integer.parseInt(matcher.group(4));
int minutes = Integer.parseInt(matcher.group(5));
if (hour > 23)
return null;
int offset = sign * ((hour * 60) + minutes) * 60000;
return new Integer(offset);
}
else if (zoneString.startsWith("GMT"))
{
return new Integer(0);
}
return null;
}
private void computeCenturyStart()
{
int year = calendar.get(Calendar.YEAR);
calendar.set(Calendar.YEAR, year - 80);
set2DigitYearStart(calendar.getTime());
}
public Object clone()
{
SimpleDateFormat clone = (SimpleDateFormat) super.clone();
clone.setDateFormatSymbols((DateFormatSymbols) formatData.clone());
clone.set2DigitYearStart((Date) defaultCenturyStart.clone());
return clone;
}
}