gcc/libjava/java/text/SimpleDateFormat.java
Tom Tromey ee9dd3721b Initial revision
From-SVN: r26263
1999-04-07 14:42:40 +00:00

523 lines
13 KiB
Java

/* Copyright (C) 1998, 1999 Cygnus Solutions
This file is part of libgcj.
This software is copyrighted work licensed under the terms of the
Libgcj License. Please consult the file "LIBGCJ_LICENSE" for
details. */
package java.text;
import java.util.*;
/**
* @author Per Bothner <bothner@cygnus.com>
* @date October 25, 1998.
*/
/* Written using "Java Class Libraries", 2nd edition, plus online
* API docs for JDK 1.2 beta from http://www.javasoft.com.
* Status: parse is not implemented.
*/
public class SimpleDateFormat extends DateFormat
{
private Date defaultCenturyStart;
private DateFormatSymbols formatData;
private String pattern;
public SimpleDateFormat ()
{
this("dd/MM/yy HH:mm", Locale.getDefault());
}
public SimpleDateFormat (String pattern)
{
this(pattern, Locale.getDefault());
}
public SimpleDateFormat (String pattern, Locale locale)
{
this.pattern = pattern;
this.calendar = Calendar.getInstance(locale);
this.numberFormat = NumberFormat.getInstance(locale);
numberFormat.setGroupingUsed(false);
this.formatData = new DateFormatSymbols (locale);
}
public SimpleDateFormat (String pattern, DateFormatSymbols formatData)
{
this.pattern = pattern;
this.formatData = formatData;
this.calendar = Calendar.getInstance();
this.numberFormat = NumberFormat.getInstance();
numberFormat.setGroupingUsed(false);
}
public Date get2DigitYearStart()
{
return defaultCenturyStart;
}
public void set2DigitYearStart(Date startDate)
{
defaultCenturyStart = startDate;
}
public DateFormatSymbols getDateFormatSymbols ()
{
return formatData;
}
public void setDateFormatSymbols (DateFormatSymbols value)
{
formatData = value;
}
public String toPattern ()
{
return pattern;
}
public void applyPattern (String pattern)
{
this.pattern = pattern;
}
private String applyLocalizedPattern (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 void applyLocalizedPattern (String pattern)
{
String localChars = formatData.getLocalPatternChars();
String standardChars = DateFormatSymbols.localPatternCharsDefault;
pattern = applyLocalizedPattern (pattern, localChars, standardChars);
applyPattern(pattern);
}
public String toLocalizedPattern ()
{
String localChars = formatData.getLocalPatternChars();
String standardChars = DateFormatSymbols.localPatternCharsDefault;
return applyLocalizedPattern (pattern, standardChars, localChars);
}
private final void append (StringBuffer buf, int value, int numDigits)
{
numberFormat.setMinimumIntegerDigits(numDigits);
numberFormat.format(value, buf, null);
}
public StringBuffer format (Date date, StringBuffer buf, FieldPosition pos)
{
Calendar calendar = (Calendar) this.calendar.clone();
calendar.setTime(date);
int len = pattern.length();
int quoteStart = -1;
for (int i = 0; i < len; i++)
{
char ch = pattern.charAt(i);
if (ch == '\'')
{
// We must do a little lookahead to see if we have two
// single quotes embedded in quoted text.
if (i < len - 1 && pattern.charAt(i + 1) == '\'')
{
++i;
buf.append(ch);
}
else
quoteStart = quoteStart < 0 ? i : -1;
}
// From JCL: any characters in the pattern that are not in
// the ranges of [a..z] and [A..Z] are treated as quoted
// text.
else if (quoteStart != -1
|| ((ch < 'a' || ch > 'z')
&& (ch < 'A' || ch > 'Z')))
buf.append(ch);
else
{
int first = i;
int value;
while (++i < len && pattern.charAt(i) == ch) ;
int count = i - first; // Number of repetions of ch in pattern.
int beginIndex = buf.length();
int field;
i--; // Skip all but last instance of ch in pattern.
switch (ch)
{
case 'd':
append(buf, calendar.get(Calendar.DATE), count);
field = DateFormat.DATE_FIELD;
break;
case 'D':
append(buf, calendar.get(Calendar.DAY_OF_YEAR), count);
field = DateFormat.DAY_OF_YEAR_FIELD;
break;
case 'F':
append(buf, calendar.get(Calendar.DAY_OF_WEEK_IN_MONTH),count);
field = DateFormat.DAY_OF_WEEK_IN_MONTH_FIELD;
break;
case 'E':
value = calendar.get(calendar.DAY_OF_WEEK);
buf.append(count <= 3 ? formatData.getShortWeekdays()[value]
: formatData.getWeekdays()[value]);
field = DateFormat.DAY_OF_WEEK_FIELD;
break;
case 'w':
append(buf, calendar.get(Calendar.WEEK_OF_YEAR), count);
field = DateFormat.WEEK_OF_YEAR_FIELD;
break;
case 'W':
append(buf, calendar.get(Calendar.WEEK_OF_MONTH), count);
field = DateFormat.WEEK_OF_MONTH_FIELD;
break;
case 'M':
value = calendar.get(Calendar.MONTH);
if (count <= 2)
append(buf, value + 1, count);
else
buf.append(count <= 3 ? formatData.getShortMonths()[value]
: formatData.getMonths()[value]);
field = DateFormat.MONTH_FIELD;
break;
case 'y':
value = calendar.get(Calendar.YEAR);
append(buf, count <= 2 ? value % 100 : value, count);
field = DateFormat.YEAR_FIELD;
break;
case 'K':
append(buf, calendar.get(Calendar.HOUR), count);
field = DateFormat.HOUR0_FIELD;
break;
case 'h':
value = ((calendar.get(Calendar.HOUR) + 11) % 12) + 1;
append(buf, value, count);
field = DateFormat.HOUR1_FIELD;
break;
case 'H':
append(buf, calendar.get(Calendar.HOUR_OF_DAY), count);
field = DateFormat.HOUR_OF_DAY0_FIELD;
break;
case 'k':
value = ((calendar.get(Calendar.HOUR_OF_DAY) + 23) % 24) + 1;
append(buf, value, count);
field = DateFormat.HOUR_OF_DAY1_FIELD;
break;
case 'm':
append(buf, calendar.get(Calendar.MINUTE), count);
field = DateFormat.MINUTE_FIELD;
break;
case 's':
append(buf, calendar.get(Calendar.SECOND), count);
field = DateFormat.SECOND_FIELD;
break;
case 'S':
append(buf, calendar.get(Calendar.MILLISECOND), count);
field = DateFormat.MILLISECOND_FIELD;
break;
case 'a':
value = calendar.get(calendar.AM_PM);
buf.append(formatData.getAmPmStrings()[value]);
field = DateFormat.AM_PM_FIELD;
break;
case 'z':
String zoneID = calendar.getTimeZone().getID();
String[][] zoneStrings = formatData.getZoneStrings();
int zoneCount = zoneStrings.length;
for (int j = 0; j < zoneCount; j++)
{
String[] strings = zoneStrings[j];
if (zoneID.equals(strings[0]))
{
j = count > 3 ? 2 : 1;
if (calendar.get(Calendar.DST_OFFSET) != 0)
j+=2;
zoneID = strings[j];
break;
}
}
buf.append(zoneID);
field = DateFormat.TIMEZONE_FIELD;
break;
default:
// Note that the JCL is actually somewhat
// contradictory here. It defines the pattern letters
// to be a particular list, but also says that a
// pattern containing an invalid pattern letter must
// throw an exception. It doesn't describe what an
// invalid pattern letter might be, so we just assume
// it is any letter in [a-zA-Z] not explicitly covered
// above.
throw new RuntimeException("bad format string");
}
if (pos != null && field == pos.getField())
{
pos.setBeginIndex(beginIndex);
pos.setEndIndex(buf.length());
}
}
}
return buf;
}
private final 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 source, ParsePosition pos)
{
int fmt_index = 0;
int fmt_max = pattern.length();
calendar.clear();
int quote_start = -1;
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 (source, 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 (source, pos, ch))
return null;
continue;
}
// We've arrived at a potential pattern character in the
// pattern.
int first = fmt_index;
while (++fmt_index < fmt_max && pattern.charAt(fmt_index) == ch)
;
int count = fmt_index - first;
--fmt_index;
// We can handle most fields automatically: most either are
// numeric or are looked up in a string vector. In some cases
// we need an offset. When numeric, `offset' is added to the
// resulting value. When doing a string lookup, offset is the
// initial index into the string array.
int calendar_field;
boolean is_numeric = true;
String[] match = null;
int offset = 0;
int zone_number = 0;
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;
match = (count <= 3
? formatData.getShortWeekdays()
: formatData.getWeekdays());
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 (count <= 2)
;
else
{
is_numeric = false;
match = (count <= 3
? formatData.getShortMonths()
: formatData.getMonths());
}
break;
case 'y':
calendar_field = Calendar.YEAR;
if (count <= 2)
offset = 1900;
break;
case 'K':
calendar_field = Calendar.HOUR;
break;
case 'h':
calendar_field = Calendar.HOUR;
offset = -1;
break;
case 'H':
calendar_field = Calendar.HOUR_OF_DAY;
break;
case 'k':
calendar_field = Calendar.HOUR_OF_DAY;
offset = -1;
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;
match = formatData.getAmPmStrings();
break;
case 'z':
// We need a special case for the timezone, because it
// uses a different data structure than the other cases.
is_numeric = false;
calendar_field = Calendar.DST_OFFSET;
String[][] zoneStrings = formatData.getZoneStrings();
int zoneCount = zoneStrings.length;
int index = pos.getIndex();
boolean found_zone = false;
for (int j = 0; j < zoneCount; j++)
{
String[] strings = zoneStrings[j];
int k;
for (k = 1; k < strings.length; ++k)
{
if (source.startsWith(strings[k], index))
break;
}
if (k != strings.length)
{
if (k > 2)
; // FIXME: dst.
zone_number = 0; // FIXME: dst.
// FIXME: raw offset to SimpleTimeZone const.
calendar.setTimeZone(new SimpleTimeZone (1, strings[0]));
pos.setIndex(index + strings[k].length());
break;
}
}
if (! found_zone)
{
pos.setErrorIndex(pos.getIndex());
return null;
}
break;
default:
pos.setErrorIndex(pos.getIndex());
return null;
}
// Compute the value we should assign to the field.
int value;
if (is_numeric)
{
numberFormat.setMinimumIntegerDigits(count);
Number n = numberFormat.parse(source, pos);
if (pos == null || ! (n instanceof Long))
return null;
value = n.intValue() + offset;
}
else if (match != null)
{
int index = pos.getIndex();
int i;
for (i = offset; i < match.length; ++i)
{
if (source.startsWith(match[i], index))
break;
}
if (i == match.length)
{
pos.setErrorIndex(index);
return null;
}
pos.setIndex(index + match[i].length());
value = i;
}
else
value = zone_number;
// Assign the value and move on.
try
{
calendar.set(calendar_field, value);
}
// FIXME: what exception is thrown on an invalid
// non-lenient set?
catch (IllegalArgumentException x)
{
pos.setErrorIndex(pos.getIndex());
return null;
}
}
return calendar.getTime();
}
public boolean equals (Object obj)
{
if (! (obj instanceof SimpleDateFormat) || ! super.equals(obj) )
return false;
SimpleDateFormat other = (SimpleDateFormat) obj;
return (DateFormatSymbols.equals(pattern, other.pattern)
&& DateFormatSymbols.equals(formatData, other.formatData)
&& DateFormatSymbols.equals(defaultCenturyStart,
other.defaultCenturyStart));
}
public int hashCode ()
{
int hash = super.hashCode();
if (pattern != null)
hash ^= pattern.hashCode();
return hash;
}
}