1 /* Copyright (C) 1998, 1999 Cygnus Solutions
3 This file is part of libgcj.
5 This software is copyrighted work licensed under the terms of the
6 Libgcj License. Please consult the file "LIBGCJ_LICENSE" for
14 * @author Per Bothner <bothner@cygnus.com>
15 * @date October 25, 1998.
17 /* Written using "Java Class Libraries", 2nd edition, plus online
18 * API docs for JDK 1.2 beta from http://www.javasoft.com.
19 * Status: parse is not implemented.
22 public class SimpleDateFormat
extends DateFormat
24 private Date defaultCenturyStart
;
25 private DateFormatSymbols formatData
;
26 private String pattern
;
28 public SimpleDateFormat ()
30 this("dd/MM/yy HH:mm", Locale
.getDefault());
33 public SimpleDateFormat (String pattern
)
35 this(pattern
, Locale
.getDefault());
38 public SimpleDateFormat (String pattern
, Locale locale
)
40 this.pattern
= pattern
;
41 this.calendar
= Calendar
.getInstance(locale
);
42 this.numberFormat
= NumberFormat
.getInstance(locale
);
43 numberFormat
.setGroupingUsed(false);
44 this.formatData
= new DateFormatSymbols (locale
);
47 public SimpleDateFormat (String pattern
, DateFormatSymbols formatData
)
49 this.pattern
= pattern
;
50 this.formatData
= formatData
;
51 this.calendar
= Calendar
.getInstance();
52 this.numberFormat
= NumberFormat
.getInstance();
53 numberFormat
.setGroupingUsed(false);
56 public Date
get2DigitYearStart()
58 return defaultCenturyStart
;
61 public void set2DigitYearStart(Date startDate
)
63 defaultCenturyStart
= startDate
;
66 public DateFormatSymbols
getDateFormatSymbols ()
71 public void setDateFormatSymbols (DateFormatSymbols value
)
76 public String
toPattern ()
81 public void applyPattern (String pattern
)
83 this.pattern
= pattern
;
86 private String
applyLocalizedPattern (String pattern
,
87 String oldChars
, String newChars
)
89 int len
= pattern
.length();
90 StringBuffer buf
= new StringBuffer(len
);
91 boolean quoted
= false;
92 for (int i
= 0; i
< len
; i
++)
94 char ch
= pattern
.charAt(i
);
99 int j
= oldChars
.indexOf(ch
);
101 ch
= newChars
.charAt(j
);
105 return buf
.toString();
108 public void applyLocalizedPattern (String pattern
)
110 String localChars
= formatData
.getLocalPatternChars();
111 String standardChars
= DateFormatSymbols
.localPatternCharsDefault
;
112 pattern
= applyLocalizedPattern (pattern
, localChars
, standardChars
);
113 applyPattern(pattern
);
116 public String
toLocalizedPattern ()
118 String localChars
= formatData
.getLocalPatternChars();
119 String standardChars
= DateFormatSymbols
.localPatternCharsDefault
;
120 return applyLocalizedPattern (pattern
, standardChars
, localChars
);
123 private final void append (StringBuffer buf
, int value
, int numDigits
)
125 numberFormat
.setMinimumIntegerDigits(numDigits
);
126 numberFormat
.format(value
, buf
, null);
129 public StringBuffer
format (Date date
, StringBuffer buf
, FieldPosition pos
)
131 Calendar calendar
= (Calendar
) this.calendar
.clone();
132 calendar
.setTime(date
);
133 int len
= pattern
.length();
135 for (int i
= 0; i
< len
; i
++)
137 char ch
= pattern
.charAt(i
);
140 // We must do a little lookahead to see if we have two
141 // single quotes embedded in quoted text.
142 if (i
< len
- 1 && pattern
.charAt(i
+ 1) == '\'')
148 quoteStart
= quoteStart
< 0 ? i
: -1;
150 // From JCL: any characters in the pattern that are not in
151 // the ranges of [a..z] and [A..Z] are treated as quoted
153 else if (quoteStart
!= -1
154 || ((ch
< 'a' || ch
> 'z')
155 && (ch
< 'A' || ch
> 'Z')))
161 while (++i
< len
&& pattern
.charAt(i
) == ch
) ;
162 int count
= i
- first
; // Number of repetions of ch in pattern.
163 int beginIndex
= buf
.length();
165 i
--; // Skip all but last instance of ch in pattern.
169 append(buf
, calendar
.get(Calendar
.DATE
), count
);
170 field
= DateFormat
.DATE_FIELD
;
173 append(buf
, calendar
.get(Calendar
.DAY_OF_YEAR
), count
);
174 field
= DateFormat
.DAY_OF_YEAR_FIELD
;
177 append(buf
, calendar
.get(Calendar
.DAY_OF_WEEK_IN_MONTH
),count
);
178 field
= DateFormat
.DAY_OF_WEEK_IN_MONTH_FIELD
;
181 value
= calendar
.get(calendar
.DAY_OF_WEEK
);
182 buf
.append(count
<= 3 ? formatData
.getShortWeekdays()[value
]
183 : formatData
.getWeekdays()[value
]);
184 field
= DateFormat
.DAY_OF_WEEK_FIELD
;
187 append(buf
, calendar
.get(Calendar
.WEEK_OF_YEAR
), count
);
188 field
= DateFormat
.WEEK_OF_YEAR_FIELD
;
191 append(buf
, calendar
.get(Calendar
.WEEK_OF_MONTH
), count
);
192 field
= DateFormat
.WEEK_OF_MONTH_FIELD
;
195 value
= calendar
.get(Calendar
.MONTH
);
197 append(buf
, value
+ 1, count
);
199 buf
.append(count
<= 3 ? formatData
.getShortMonths()[value
]
200 : formatData
.getMonths()[value
]);
201 field
= DateFormat
.MONTH_FIELD
;
204 value
= calendar
.get(Calendar
.YEAR
);
205 append(buf
, count
<= 2 ? value
% 100 : value
, count
);
206 field
= DateFormat
.YEAR_FIELD
;
209 append(buf
, calendar
.get(Calendar
.HOUR
), count
);
210 field
= DateFormat
.HOUR0_FIELD
;
213 value
= ((calendar
.get(Calendar
.HOUR
) + 11) % 12) + 1;
214 append(buf
, value
, count
);
215 field
= DateFormat
.HOUR1_FIELD
;
218 append(buf
, calendar
.get(Calendar
.HOUR_OF_DAY
), count
);
219 field
= DateFormat
.HOUR_OF_DAY0_FIELD
;
222 value
= ((calendar
.get(Calendar
.HOUR_OF_DAY
) + 23) % 24) + 1;
223 append(buf
, value
, count
);
224 field
= DateFormat
.HOUR_OF_DAY1_FIELD
;
227 append(buf
, calendar
.get(Calendar
.MINUTE
), count
);
228 field
= DateFormat
.MINUTE_FIELD
;
231 append(buf
, calendar
.get(Calendar
.SECOND
), count
);
232 field
= DateFormat
.SECOND_FIELD
;
235 append(buf
, calendar
.get(Calendar
.MILLISECOND
), count
);
236 field
= DateFormat
.MILLISECOND_FIELD
;
239 value
= calendar
.get(calendar
.AM_PM
);
240 buf
.append(formatData
.getAmPmStrings()[value
]);
241 field
= DateFormat
.AM_PM_FIELD
;
244 String zoneID
= calendar
.getTimeZone().getID();
245 String
[][] zoneStrings
= formatData
.getZoneStrings();
246 int zoneCount
= zoneStrings
.length
;
247 for (int j
= 0; j
< zoneCount
; j
++)
249 String
[] strings
= zoneStrings
[j
];
250 if (zoneID
.equals(strings
[0]))
252 j
= count
> 3 ?
2 : 1;
253 if (calendar
.get(Calendar
.DST_OFFSET
) != 0)
260 field
= DateFormat
.TIMEZONE_FIELD
;
263 // Note that the JCL is actually somewhat
264 // contradictory here. It defines the pattern letters
265 // to be a particular list, but also says that a
266 // pattern containing an invalid pattern letter must
267 // throw an exception. It doesn't describe what an
268 // invalid pattern letter might be, so we just assume
269 // it is any letter in [a-zA-Z] not explicitly covered
271 throw new RuntimeException("bad format string");
273 if (pos
!= null && field
== pos
.getField())
275 pos
.setBeginIndex(beginIndex
);
276 pos
.setEndIndex(buf
.length());
283 private final boolean expect (String source
, ParsePosition pos
,
286 int x
= pos
.getIndex();
287 boolean r
= x
< source
.length() && source
.charAt(x
) == ch
;
291 pos
.setErrorIndex(x
);
295 public Date
parse (String source
, ParsePosition pos
)
298 int fmt_max
= pattern
.length();
301 int quote_start
= -1;
302 for (; fmt_index
< fmt_max
; ++fmt_index
)
304 char ch
= pattern
.charAt(fmt_index
);
307 int index
= pos
.getIndex();
308 if (fmt_index
< fmt_max
- 1
309 && pattern
.charAt(fmt_index
+ 1) == '\'')
311 if (! expect (source
, pos
, ch
))
316 quote_start
= quote_start
< 0 ? fmt_index
: -1;
320 if (quote_start
!= -1
321 || ((ch
< 'a' || ch
> 'z')
322 && (ch
< 'A' || ch
> 'Z')))
324 if (! expect (source
, pos
, ch
))
329 // We've arrived at a potential pattern character in the
331 int first
= fmt_index
;
332 while (++fmt_index
< fmt_max
&& pattern
.charAt(fmt_index
) == ch
)
334 int count
= fmt_index
- first
;
337 // We can handle most fields automatically: most either are
338 // numeric or are looked up in a string vector. In some cases
339 // we need an offset. When numeric, `offset' is added to the
340 // resulting value. When doing a string lookup, offset is the
341 // initial index into the string array.
343 boolean is_numeric
= true;
344 String
[] match
= null;
350 calendar_field
= Calendar
.DATE
;
353 calendar_field
= Calendar
.DAY_OF_YEAR
;
356 calendar_field
= Calendar
.DAY_OF_WEEK_IN_MONTH
;
361 calendar_field
= Calendar
.DAY_OF_WEEK
;
363 ? formatData
.getShortWeekdays()
364 : formatData
.getWeekdays());
367 calendar_field
= Calendar
.WEEK_OF_YEAR
;
370 calendar_field
= Calendar
.WEEK_OF_MONTH
;
373 calendar_field
= Calendar
.MONTH
;
380 ? formatData
.getShortMonths()
381 : formatData
.getMonths());
385 calendar_field
= Calendar
.YEAR
;
390 calendar_field
= Calendar
.HOUR
;
393 calendar_field
= Calendar
.HOUR
;
397 calendar_field
= Calendar
.HOUR_OF_DAY
;
400 calendar_field
= Calendar
.HOUR_OF_DAY
;
404 calendar_field
= Calendar
.MINUTE
;
407 calendar_field
= Calendar
.SECOND
;
410 calendar_field
= Calendar
.MILLISECOND
;
414 calendar_field
= Calendar
.AM_PM
;
415 match
= formatData
.getAmPmStrings();
418 // We need a special case for the timezone, because it
419 // uses a different data structure than the other cases.
421 calendar_field
= Calendar
.DST_OFFSET
;
422 String
[][] zoneStrings
= formatData
.getZoneStrings();
423 int zoneCount
= zoneStrings
.length
;
424 int index
= pos
.getIndex();
425 boolean found_zone
= false;
426 for (int j
= 0; j
< zoneCount
; j
++)
428 String
[] strings
= zoneStrings
[j
];
430 for (k
= 1; k
< strings
.length
; ++k
)
432 if (source
.startsWith(strings
[k
], index
))
435 if (k
!= strings
.length
)
439 zone_number
= 0; // FIXME: dst.
440 // FIXME: raw offset to SimpleTimeZone const.
441 calendar
.setTimeZone(new SimpleTimeZone (1, strings
[0]));
442 pos
.setIndex(index
+ strings
[k
].length());
448 pos
.setErrorIndex(pos
.getIndex());
453 pos
.setErrorIndex(pos
.getIndex());
457 // Compute the value we should assign to the field.
461 numberFormat
.setMinimumIntegerDigits(count
);
462 Number n
= numberFormat
.parse(source
, pos
);
463 if (pos
== null || ! (n
instanceof Long
))
465 value
= n
.intValue() + offset
;
467 else if (match
!= null)
469 int index
= pos
.getIndex();
471 for (i
= offset
; i
< match
.length
; ++i
)
473 if (source
.startsWith(match
[i
], index
))
476 if (i
== match
.length
)
478 pos
.setErrorIndex(index
);
481 pos
.setIndex(index
+ match
[i
].length());
487 // Assign the value and move on.
490 calendar
.set(calendar_field
, value
);
492 // FIXME: what exception is thrown on an invalid
494 catch (IllegalArgumentException x
)
496 pos
.setErrorIndex(pos
.getIndex());
501 return calendar
.getTime();
504 public boolean equals (Object obj
)
506 if (! (obj
instanceof SimpleDateFormat
) || ! super.equals(obj
) )
508 SimpleDateFormat other
= (SimpleDateFormat
) obj
;
509 return (DateFormatSymbols
.equals(pattern
, other
.pattern
)
510 && DateFormatSymbols
.equals(formatData
, other
.formatData
)
511 && DateFormatSymbols
.equals(defaultCenturyStart
,
512 other
.defaultCenturyStart
));
515 public int hashCode ()
517 int hash
= super.hashCode();
519 hash ^
= pattern
.hashCode();