/************************************************************************** * Parts copyright (c) 2001 by Punch Telematix. All rights reserved. * * Parts copyright (c) 2008 by Chris Gray, /k/ Embedded Java Solutions. * * All rights reserved. * * * * Redistribution and use in source and binary forms, with or without * * modification, are permitted provided that the following conditions * * are met: * * 1. Redistributions of source code must retain the above copyright * * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * * notice, this list of conditions and the following disclaimer in the * * documentation and/or other materials provided with the distribution. * * 3. Neither the name of Punch Telematix or of /k/ Embedded Java Solutions* * nor the names of other contributors may be used to endorse or promote* * products derived from this software without specific prior written * * permission. * * * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED * * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * * IN NO EVENT SHALL PUNCH TELEMATIX, /K/ EMBEDDED JAVA SOLUTIONS OR OTHER * * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * **************************************************************************/ package java.text; import java.util.*; import java.io.IOException; import java.io.ObjectInputStream; public class SimpleDateFormat extends DateFormat { static final FieldPosition TRASHPOSITION = new FieldPosition(0); private static final long serialVersionUID = 4774881970558875024L; private static final DateFormatSymbols DEFAULTSYMBOLS = new DateFormatSymbols(); private static final String PATTERNCHARS = "GyMdkHmsSEDFwWahKzZ"; private static final int[] FIELDMAP = { 0, 1, 2, 5, 11, 11, 12, 13, 14, 7, 6, 8, 3, 4, 9, 10, 10, 10 }; //dictated by the serialized form ... private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException{ in.defaultReadObject(); if(serialVersionOnStream < 1){ serialVersionOnStream = 1; defaultCenturyStart = new Date(); } } private Date defaultCenturyStart = new Date(); private DateFormatSymbols formatData; private String pattern; private int serialVersionOnStream = 1; public SimpleDateFormat(){ this("dd.MM.yy hh:mm:ss",new DateFormatSymbols()); } public SimpleDateFormat(String pattern){ this(pattern, new DateFormatSymbols()); } public SimpleDateFormat(String pattern, Locale loc){ this(pattern, new DateFormatSymbols(loc)); } public SimpleDateFormat(String pattern, DateFormatSymbols dfs){ if(pattern == null || dfs == null){ throw new NullPointerException(); } calendar = Calendar.getInstance(); numberFormat = NumberFormat.getInstance(); numberFormat.setParseIntegerOnly(true); formatData = dfs; this.pattern = pattern; } public void applyLocalizedPattern(String pattern){ String local = formatData.getLocalPatternChars(); this.pattern = toPattern(pattern, local, PATTERNCHARS); } public void applyPattern(String pattern){ if (pattern == null){ throw new NullPointerException(); } this.pattern = pattern; } public Object clone() { SimpleDateFormat sdf = (SimpleDateFormat) super.clone(); sdf.formatData = (DateFormatSymbols) formatData.clone(); sdf.defaultCenturyStart = (Date) defaultCenturyStart.clone(); return sdf; } public boolean equals(Object o){ if(!(o instanceof SimpleDateFormat)){ return false; } SimpleDateFormat sdf = (SimpleDateFormat)o; return this.pattern.equals(sdf.pattern); } public StringBuffer format(Date date, StringBuffer dest, FieldPosition pos){ //TODO: take min en max digits into account !!! calendar.setTime(date); //System.out.println("formatting date "+date+" to --> "+pattern); int size = pattern.length(); boolean formatting = true; for(int i = 0 ; i < size ; i++){ char ch = pattern.charAt(i); //System.out.println("pattern char = '"+ch+"' at ("+i+") formatting "+formatting); if(ch == '\''){ // lets find quotes ... if((++i) < size && pattern.charAt(i) == ch){ dest.append(ch); } else { i--; formatting = !formatting; } } else { if(formatting){ int field = PATTERNCHARS.indexOf(ch); if(field != -1){ i = writeField(ch, i, field, dest, pos); //System.out.println("writeField stopped at "+i); continue; } } //System.out.println("appending "+ch); dest.append(ch); } } //System.out.println(dest); return dest; } public DateFormatSymbols getDateFormatSymbols(){ return formatData; } public Date get2DigitYearStart(){ return defaultCenturyStart; } public int hashCode(){ return pattern.hashCode() ^ 0xaaaaaaaa; } public Date parse(String str, ParsePosition pos) { //System.out.println("parsing '"+str+"' ("+pos.getIndex()+") using "+pattern); calendar.clear(); TimeZone current = calendar.getTimeZone(); int size = pattern.length(); boolean formatting = true; int p = pos.getIndex(); boolean checkDST = false; try { for(int i = 0 ; i < size ; i++,p++){ char ch = pattern.charAt(i); //System.out.println("pattern char = '"+ch+"' at ("+i+") formatting "+formatting); if(ch == '\''){ // lets find quotes ... if((++i) < size && pattern.charAt(i) == ch){ if(str.charAt(p) != ch){ pos.setIndex(p); pos.setErrorIndex(p); //System.out.println("PARSE ERROR -- MISSING QUOTE"); return null; } } else { i--; formatting = !formatting; p--; } } else { if(formatting){ int field = PATTERNCHARS.indexOf(ch); if(field != -1){ pos.setIndex(p); if (field == 17 || field == 18) { // special case: z/Z int nr = 1; for (++i; i < size ; i++){ if(pattern.charAt(i) == ch){ nr++; } else{ break; } } i--; int res = -1; if (str.charAt(p) == '+' || str.charAt(p) == '-') { String syntheticTZ = "GMT" + str.substring(p, p + 3) + ":" + str.substring(p + 3); //System.out.println("using synthetic timezone " + syntheticTZ); calendar.setTimeZone(TimeZone.getTimeZone(syntheticTZ)); p += 5; res = 0; } else { res = formatData.parseTimeZoneString(calendar, str, pos); p = pos.getIndex()-1; } if(res == -1){ //System.out.println("time zone not found"); return null; } checkDST = (res == 1); continue; } else { i = readField(ch, i, field, str,pos); //System.out.println("readField stopped at "+i); if(i == -1){ //System.out.println("PARSE ERROR -- READFIELD FAILED"); return null; } p = pos.getIndex()-1; //System.out.println("readField stopped at "+i+"("+pattern.length()+") new position is "+(p+1)+" "+str.substring(0,p+1) + "^" + str.substring(p+1)); continue; } } } if(str.charAt(p) != ch){ pos.setIndex(p); pos.setErrorIndex(p); //System.out.println("PARSE ERROR -- WRONG CHAR ENCOUNTERED"); return null; } } } pos.setIndex(p); } catch (IndexOutOfBoundsException ioobe) { pos.setErrorIndex(p); //System.out.println("PARSE ERROR -- premature end of input at position " + p); return null; } catch (NumberFormatException nfe) { pos.setErrorIndex(p); //System.out.println("PARSE ERROR -- number format error at position " + p); return null; } //System.out.println("parsed '"+str+"' to "+calendar.getTime()); Date time = calendar.getTime(); if(checkDST && calendar.getTimeZone().inDaylightTime(time)){ time.setTime(time.getTime()+((SimpleTimeZone)calendar.getTimeZone()).getDSTSavings()); } calendar.setTimeZone(current); return time; } public void setDateFormatSymbols(DateFormatSymbols dfs){ formatData = dfs; } public void set2DigitYearStart(Date date){ if(date == null){ throw new NullPointerException(); } defaultCenturyStart = date; } public String toLocalizedPattern(){ return toPattern(pattern, PATTERNCHARS, formatData.getLocalPatternChars()); } public String toPattern(){ return pattern; } private String toPattern(String pattern, String from, String to){ int size = pattern.length(); char[] chars = new char[size]; pattern.getChars(0, size, chars, 0); for(int i = 0 ; i < size ; i++){ char ch = chars[i]; if(ch == '\''){ // lets find quotes ... int q = pattern.indexOf(ch, i+1); if(i+1 == q){ //we found two consecutive quotes i++; } else{//we have a quoted string ... while(q != -1 && (++q) < size && ch == pattern.charAt(q)){ q = pattern.indexOf(ch, q+1); } i = (q == -1 ? size : q-1); } } else { int p = from.indexOf(ch); if(p != -1){ chars[i] = to.charAt(p); } } } return new String(chars,0,size); } private int readField(char ch, int idx, int field, String dest, ParsePosition pos){ //System.out.println("reading field "+ch+" ("+field+")"); int nr = 1; int i = idx+1; int pattern_length = pattern.length(); int res; for (; i < pattern_length ; i++){ if(pattern.charAt(i) == ch){ nr++; } else{ break; } } idx = i-1; switch(ch){ case 'M': //MONTH if(nr < 3){ numberFormat.minimumIntegerDigits = nr; numberFormat.maximumIntegerDigits = 2; Number num1 = numberFormat.parse(dest, pos); if(num1 != null){ calendar.set(Calendar.MONTH,num1.intValue()-1); } else { return -1; } } else { res = arrayLookup(formatData.getMonths(), dest, pos,field, idx, true); if (res >= 0) { return res; } return arrayLookup(formatData.getShortMonths(), dest, pos,field, idx, true); } break; case 'y': numberFormat.minimumIntegerDigits = nr; numberFormat.maximumIntegerDigits = nr == 2 ? 2 : (nr > 4 ? nr : 4); Number num2 = numberFormat.parse(dest, pos); if(num2 != null){ int year = num2.intValue(); if(nr < 3){ GregorianCalendar gc = new GregorianCalendar(0,0,0); gc.setTime(defaultCenturyStart); int cent = gc.get(Calendar.YEAR); year += cent - (cent % 100); } calendar.set(Calendar.YEAR, year); } else { return -1; } break; case 'd': //2 case 'H': case 'K': case 'w': case 'W': case 'm': case 's': numberFormat.minimumIntegerDigits = nr; numberFormat.maximumIntegerDigits = nr > 2 ? nr : 2; Number num3 = numberFormat.parse(dest, pos); if(num3 != null){ //System.out.println("setting "+FIELDMAP[field]+" to "+num3.intValue()); calendar.set(FIELDMAP[field],num3.intValue()); } else { return -1; } break; case 'D': //3 numberFormat.minimumIntegerDigits = nr; numberFormat.maximumIntegerDigits = nr > 3 ? nr : 3; Number num4 = numberFormat.parse(dest, pos); if(num4 != null){ //System.out.println("setting "+FIELDMAP[field]+" to "+num4.intValue()); calendar.set(FIELDMAP[field],num4.intValue()); } else { return -1; } break; case 'S': //always 3 numberFormat.minimumIntegerDigits = 3; numberFormat.maximumIntegerDigits = 3; Number num5 = numberFormat.parse(dest, pos); if(num5 != null){ //System.out.println("setting "+FIELDMAP[field]+" to "+num5.intValue()); calendar.set(FIELDMAP[field],num5.intValue()); } else { return -1; } break; case 'F': //1 numberFormat.minimumIntegerDigits = nr; numberFormat.maximumIntegerDigits = nr; Number num6 = numberFormat.parse(dest, pos); if(num6 != null){ //System.out.println("setting "+FIELDMAP[field]+" to "+num6.intValue()); calendar.set(FIELDMAP[field],num6.intValue()); } else { return -1; } break; case 'a': return arrayLookup(formatData.getAmPmStrings(), dest, pos, field, idx, true); case 'E': res = arrayLookup(formatData.getWeekdays(), dest, pos, field, idx, false); if(res != -1){ return res; } return arrayLookup(formatData.getShortWeekdays(), dest, pos, field, idx, true); case 'h': numberFormat.minimumIntegerDigits = nr; numberFormat.maximumIntegerDigits = nr > 2 ? nr : 2; Number num7 = numberFormat.parse(dest, pos); if(num7 != null){ calendar.set(FIELDMAP[field],num7.intValue()%12); } else { return -1; } break; case 'k': Number num8 = numberFormat.parse(dest, pos); if(num8 != null){ calendar.set(FIELDMAP[field],num8.intValue()%24); } else { return -1; } break; case 'G': //ERA return arrayLookup(formatData.getEras(), dest, pos, field, idx, true); default: //System.out.println("got a bad character"); return -1; } return idx; } private int arrayLookup(String[] strings, String source, ParsePosition pos, int field, int idx, boolean set){ int start = pos.getIndex(); for(int i = 0 ; i < strings.length ; i++){ int len = strings[i].length(); //System.out.println("checking '"+source+"'("+start+") for '"+strings[i]); // [CG 20081204] apparently we need to be case-insensitive here // WAS: if(len > 0 && source.regionMatches(start, strings[i], 0, len)){ if(len > 0 && source.length() >= start + len && strings[i].length() >= len && source.substring(start, start + len).equalsIgnoreCase(strings[i].substring(0, len))) { calendar.set(FIELDMAP[field], i); pos.setIndex(start+len); return idx; } } if(set){ pos.setErrorIndex(start); } return -1; } private int writeField(char ch, int idx, int field, StringBuffer dest, FieldPosition pos){ //System.out.println("Writing field "+ch+" ("+field+")"); int nr = 1; int start = dest.length(); int i = idx+1; int pattern_length = pattern.length(); for (; i < pattern_length ; i++){ if(pattern.charAt(i) == ch){ nr++; } else{ break; } } idx = i-1; switch(ch){ case 'M': //MONTH int m = calendar.get(Calendar.MONTH); if(nr < 3){ numberFormat.setMinimumIntegerDigits(nr); numberFormat.format(m+1, dest, TRASHPOSITION); } else { String[] ms = (nr == 3 ? formatData.getShortMonths() : formatData.getMonths()); dest.append(ms[m]); } break; case 'y': numberFormat.setMinimumIntegerDigits(nr); numberFormat.format(calendar.get(Calendar.YEAR), dest, TRASHPOSITION); if(nr < 4){ dest.delete(start,dest.length()-2); } break; case 'd': //2 case 'H': case 'K': case 'w': case 'W': case 'm': case 's': numberFormat.setMinimumIntegerDigits(2 < nr ? 2 : nr); numberFormat.format(calendar.get(FIELDMAP[field]), dest, TRASHPOSITION); break; case 'D': //3 numberFormat.setMinimumIntegerDigits(3 < nr ? 3 : nr); numberFormat.format(calendar.get(FIELDMAP[field]), dest, TRASHPOSITION); break; case 'F': //1 numberFormat.setMinimumIntegerDigits(1); numberFormat.format(calendar.get(FIELDMAP[field]), dest, TRASHPOSITION); break; case 'a': dest.append(formatData.getAmPmStrings()[calendar.get(Calendar.AM_PM)]); break; case 'E': String[] ms = (nr <= 3 ? formatData.getShortWeekdays() : formatData.getWeekdays()); dest.append(ms[calendar.get(Calendar.DAY_OF_WEEK)]); break; case 'z': dest.append(formatData.getTimeZoneString(calendar, nr > 3)); break; case 'S': numberFormat.setMinimumIntegerDigits(3); numberFormat.format(calendar.get(Calendar.MILLISECOND), dest, TRASHPOSITION); break; case 'h': numberFormat.setMinimumIntegerDigits(nr); int h = calendar.get(FIELDMAP[field]); numberFormat.format((h == 0 ? 12 : h) , dest, TRASHPOSITION); break; case 'k': numberFormat.setMinimumIntegerDigits(nr); int k = calendar.get(FIELDMAP[field]); numberFormat.format((k == 0 ? 24 : k) , dest, TRASHPOSITION); break; case 'G': //ERA dest.append(formatData.getEras()[calendar.get(Calendar.ERA)]); break; } if(field == pos.getField()){ pos.setBeginIndex(start); pos.setEndIndex(dest.length()); } return idx; } }