michael@0: /** michael@0: * Copyright (c) 2012, Ben Fortuna michael@0: * All rights reserved. michael@0: * michael@0: * Redistribution and use in source and binary forms, with or without michael@0: * modification, are permitted provided that the following conditions michael@0: * are met: michael@0: * michael@0: * o Redistributions of source code must retain the above copyright michael@0: * notice, this list of conditions and the following disclaimer. michael@0: * michael@0: * o Redistributions in binary form must reproduce the above copyright michael@0: * notice, this list of conditions and the following disclaimer in the michael@0: * documentation and/or other materials provided with the distribution. michael@0: * michael@0: * o Neither the name of Ben Fortuna nor the names of any other contributors michael@0: * may be used to endorse or promote products derived from this software michael@0: * without specific prior written permission. michael@0: * michael@0: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS michael@0: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT michael@0: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR michael@0: * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR michael@0: * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, michael@0: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, michael@0: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR michael@0: * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF michael@0: * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING michael@0: * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS michael@0: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. michael@0: */ michael@0: package net.fortuna.ical4j.model; michael@0: michael@0: import java.text.FieldPosition; michael@0: import java.text.NumberFormat; michael@0: import java.text.ParsePosition; michael@0: import java.text.SimpleDateFormat; michael@0: import java.util.Date; michael@0: import java.util.GregorianCalendar; michael@0: import java.util.TimeZone; michael@0: michael@0: import org.apache.commons.logging.Log; michael@0: import org.apache.commons.logging.LogFactory; michael@0: michael@0: /** michael@0: * $Id$ [06-Apr-2004] michael@0: * michael@0: * Creates DateFormat objects optimized for common iCalendar date patterns. michael@0: * michael@0: * @author Dave Nault dnault@laszlosystems.com michael@0: * @see #getInstance(String) michael@0: */ michael@0: public final class CalendarDateFormatFactory { michael@0: private static final Log LOG = LogFactory.getLog(CalendarDateFormatFactory.class); michael@0: michael@0: private static final String DATETIME_PATTERN = "yyyyMMdd'T'HHmmss"; michael@0: private static final String DATETIME_UTC_PATTERN = "yyyyMMdd'T'HHmmss'Z'"; michael@0: private static final String DATE_PATTERN = "yyyyMMdd"; michael@0: private static final String TIME_PATTERN = "HHmmss"; michael@0: private static final String TIME_UTC_PATTERN = "HHmmss'Z'"; michael@0: michael@0: /** michael@0: * Constructor made private to enforce static nature. michael@0: */ michael@0: private CalendarDateFormatFactory() { michael@0: } michael@0: michael@0: /** michael@0: * Returns DateFormat objects optimized for common iCalendar date patterns. The DateFormats are *not* thread safe. michael@0: * Attempts to get or set the Calendar or NumberFormat of an optimized DateFormat will result in an michael@0: * UnsupportedOperation exception being thrown. michael@0: * michael@0: * @param pattern michael@0: * a SimpleDateFormat-compatible pattern michael@0: * @return an optimized DateFormat instance if possible, otherwise a normal SimpleDateFormat instance michael@0: */ michael@0: public static java.text.DateFormat getInstance(String pattern) { michael@0: java.text.DateFormat instance = null; michael@0: michael@0: // if (true) { michael@0: // return new SimpleDateFormat(pattern); michael@0: // } michael@0: michael@0: if (pattern.equals(DATETIME_PATTERN) || pattern.equals(DATETIME_UTC_PATTERN)) { michael@0: instance = new DateTimeFormat(pattern); michael@0: } michael@0: else if (pattern.equals(DATE_PATTERN)) { michael@0: instance = new DateFormat(pattern); michael@0: } michael@0: else if (pattern.equals(TIME_PATTERN) || pattern.equals(TIME_UTC_PATTERN)) { michael@0: instance = new TimeFormat(pattern); michael@0: } michael@0: else { michael@0: if (LOG.isDebugEnabled()) { michael@0: LOG.debug("unexpected date format pattern: " + pattern); michael@0: } michael@0: michael@0: instance = new SimpleDateFormat(pattern); michael@0: } michael@0: return instance; michael@0: } michael@0: michael@0: private abstract static class CalendarDateFormat extends java.text.DateFormat { michael@0: /** michael@0: * michael@0: */ michael@0: private static final long serialVersionUID = -4191402739860280205L; michael@0: michael@0: private static final java.util.TimeZone DEFAULT_TIME_ZONE = TimeZone.getDefault(); michael@0: michael@0: private final String pattern; michael@0: michael@0: private boolean lenient = true; michael@0: michael@0: private java.util.TimeZone timeZone = DEFAULT_TIME_ZONE; michael@0: michael@0: public CalendarDateFormat(String pattern) { michael@0: this.pattern = pattern; michael@0: } michael@0: michael@0: public java.util.TimeZone getTimeZone() { michael@0: return this.timeZone; michael@0: } michael@0: michael@0: public void setTimeZone(java.util.TimeZone tz) { michael@0: this.timeZone = tz; michael@0: } michael@0: michael@0: public void setLenient(boolean lenient) { michael@0: this.lenient = lenient; michael@0: } michael@0: michael@0: public boolean isLenient() { michael@0: return lenient; michael@0: } michael@0: michael@0: public java.util.Calendar getCalendar() { michael@0: throw new UnsupportedOperationException(); michael@0: } michael@0: michael@0: public void setCalendar(java.util.Calendar c) { michael@0: throw new UnsupportedOperationException(); michael@0: } michael@0: michael@0: public NumberFormat getNumberFormat() { michael@0: throw new UnsupportedOperationException(); michael@0: } michael@0: michael@0: public void setNumberFormat(NumberFormat n) { michael@0: throw new UnsupportedOperationException(); michael@0: } michael@0: michael@0: public Object clone() { michael@0: // don't call super.clone() michael@0: final CalendarDateFormat f = (CalendarDateFormat) CalendarDateFormatFactory.getInstance(pattern); michael@0: f.setTimeZone(getTimeZone()); michael@0: f.setLenient(isLenient()); michael@0: return f; michael@0: } michael@0: michael@0: public boolean equals(Object o) { michael@0: if (this == o) { michael@0: return true; michael@0: } michael@0: if (o == null || getClass() != o.getClass()) { michael@0: return false; michael@0: } michael@0: if (!super.equals(o)) { michael@0: return false; michael@0: } michael@0: michael@0: final CalendarDateFormat that = (CalendarDateFormat) o; michael@0: michael@0: if (lenient != that.lenient) { michael@0: return false; michael@0: } michael@0: if (!pattern.equals(that.pattern)) { michael@0: return false; michael@0: } michael@0: if (!timeZone.equals(that.timeZone)) { michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: public int hashCode() { michael@0: int result = super.hashCode(); michael@0: result = 31 * result + pattern.hashCode(); michael@0: result = 31 * result + (lenient ? 1 : 0); michael@0: result = 31 * result + timeZone.hashCode(); michael@0: return result; michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * A custom date-time formatter. michael@0: * Parses and formats these patterns: michael@0: * michael@0: *
michael@0:      * yyyyMMdd'T'HHmmss
michael@0:      * yyyyMMdd'T'HHmmss'Z'
michael@0:      * 
michael@0: */ michael@0: private static class DateTimeFormat extends CalendarDateFormat { michael@0: michael@0: /** michael@0: * michael@0: */ michael@0: private static final long serialVersionUID = 3005824302269636122L; michael@0: michael@0: final boolean patternEndsWithZ; michael@0: michael@0: public DateTimeFormat(String pattern) { michael@0: super(pattern); michael@0: patternEndsWithZ = pattern.endsWith("'Z'"); michael@0: } michael@0: michael@0: public StringBuffer format(Date date, StringBuffer toAppendTo, FieldPosition fieldPosition) { michael@0: final java.util.Calendar cal = new GregorianCalendar(getTimeZone()); michael@0: cal.setTimeInMillis(date.getTime()); michael@0: michael@0: appendPadded(toAppendTo, cal.get(GregorianCalendar.YEAR), 4); michael@0: appendPadded(toAppendTo, cal.get(GregorianCalendar.MONTH) + 1, 2); michael@0: appendPadded(toAppendTo, cal.get(GregorianCalendar.DAY_OF_MONTH), 2); michael@0: toAppendTo.append("T"); michael@0: michael@0: appendPadded(toAppendTo, cal.get(GregorianCalendar.HOUR_OF_DAY), 2); michael@0: appendPadded(toAppendTo, cal.get(GregorianCalendar.MINUTE), 2); michael@0: appendPadded(toAppendTo, cal.get(GregorianCalendar.SECOND), 2); michael@0: michael@0: if (patternEndsWithZ) { michael@0: toAppendTo.append("Z"); michael@0: } michael@0: michael@0: return toAppendTo; michael@0: } michael@0: michael@0: public Date parse(String source, ParsePosition pos) { michael@0: // if lenient ignore superfluous input.. michael@0: if (patternEndsWithZ) { michael@0: if (source.length() > DATETIME_UTC_PATTERN.length() && !isLenient()) { michael@0: pos.setErrorIndex(DATETIME_UTC_PATTERN.length()); michael@0: return null; michael@0: } michael@0: } else if (source.length() > DATETIME_PATTERN.length() && !isLenient()) { michael@0: pos.setErrorIndex(DATETIME_PATTERN.length()); michael@0: return null; michael@0: } michael@0: michael@0: try { michael@0: if (source.charAt(8) != 'T') { michael@0: pos.setErrorIndex(8); michael@0: return null; michael@0: } michael@0: if (patternEndsWithZ && source.charAt(15) != 'Z') { michael@0: pos.setErrorIndex(15); michael@0: return null; michael@0: } michael@0: michael@0: final int year = Integer.parseInt(source.substring(0, 4)); michael@0: final int month = Integer.parseInt(source.substring(4, 6)) - 1; michael@0: final int day = Integer.parseInt(source.substring(6, 8)); michael@0: final int hour = Integer.parseInt(source.substring(9, 11)); michael@0: final int minute = Integer.parseInt(source.substring(11, 13)); michael@0: final int second = Integer.parseInt(source.substring(13, 15)); michael@0: michael@0: final Date d = makeCalendar(isLenient(), getTimeZone(), michael@0: year, month, day, hour, minute, second).getTime(); michael@0: pos.setIndex(15); michael@0: return d; michael@0: } catch (Exception e) { michael@0: return null; michael@0: } michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Custom date formatter. michael@0: * Parses and formats this pattern: michael@0: * michael@0: *
michael@0:      * yyyyMMdd
michael@0:      * 
michael@0: */ michael@0: private static class DateFormat extends CalendarDateFormat { michael@0: michael@0: /** michael@0: * michael@0: */ michael@0: private static final long serialVersionUID = -7626077667268431779L; michael@0: michael@0: public DateFormat(String pattern) { michael@0: super(pattern); michael@0: } michael@0: michael@0: public StringBuffer format(Date date, StringBuffer toAppendTo, FieldPosition fieldPosition) { michael@0: final java.util.Calendar cal = java.util.Calendar.getInstance(getTimeZone()); michael@0: cal.setTimeInMillis(date.getTime()); michael@0: michael@0: appendPadded(toAppendTo, cal.get(GregorianCalendar.YEAR), 4); michael@0: appendPadded(toAppendTo, cal.get(GregorianCalendar.MONTH) + 1, 2); michael@0: appendPadded(toAppendTo, cal.get(GregorianCalendar.DAY_OF_MONTH), 2); michael@0: michael@0: return toAppendTo; michael@0: } michael@0: michael@0: public Date parse(String source, ParsePosition pos) { michael@0: // if lenient ignore superfluous input.. michael@0: if (source.length() > DATE_PATTERN.length() && !isLenient()) { michael@0: pos.setErrorIndex(DATE_PATTERN.length()); michael@0: return null; michael@0: } michael@0: michael@0: try { michael@0: final int year = Integer.parseInt(source.substring(0, 4)); michael@0: final int month = Integer.parseInt(source.substring(4, 6)) - 1; michael@0: final int day = Integer.parseInt(source.substring(6, 8)); michael@0: michael@0: final Date d = makeCalendar(isLenient(), getTimeZone(), year, month, day).getTime(); michael@0: pos.setIndex(8); michael@0: return d; michael@0: } catch (Exception e) { michael@0: return null; michael@0: } michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Custom time formatter. michael@0: * Parses and formats these patterns: michael@0: * michael@0: *
michael@0:      * HHmmss
michael@0:      * HHmmss'Z'
michael@0:      * 
michael@0: */ michael@0: private static class TimeFormat extends CalendarDateFormat { michael@0: michael@0: /** michael@0: * michael@0: */ michael@0: private static final long serialVersionUID = -1367114409994225425L; michael@0: michael@0: final boolean patternEndsWithZ; michael@0: michael@0: public TimeFormat(String pattern) { michael@0: super(pattern); michael@0: patternEndsWithZ = pattern.endsWith("'Z'"); michael@0: } michael@0: michael@0: public StringBuffer format(Date date, StringBuffer toAppendTo, FieldPosition fieldPosition) { michael@0: final java.util.Calendar cal = new GregorianCalendar(getTimeZone()); michael@0: cal.setTimeInMillis(date.getTime()); michael@0: michael@0: appendPadded(toAppendTo, cal.get(GregorianCalendar.HOUR_OF_DAY), 2); michael@0: appendPadded(toAppendTo, cal.get(GregorianCalendar.MINUTE), 2); michael@0: appendPadded(toAppendTo, cal.get(GregorianCalendar.SECOND), 2); michael@0: michael@0: if (patternEndsWithZ) { michael@0: toAppendTo.append("Z"); michael@0: } michael@0: michael@0: return toAppendTo; michael@0: } michael@0: michael@0: public Date parse(String source, ParsePosition pos) { michael@0: // if lenient ignore superfluous input.. michael@0: if (patternEndsWithZ) { michael@0: if (source.length() > TIME_UTC_PATTERN.length() && !isLenient()) { michael@0: pos.setErrorIndex(TIME_UTC_PATTERN.length()); michael@0: return null; michael@0: } michael@0: } else if (source.length() > TIME_PATTERN.length() && !isLenient()) { michael@0: pos.setErrorIndex(TIME_PATTERN.length()); michael@0: return null; michael@0: } michael@0: michael@0: try { michael@0: if (patternEndsWithZ && source.charAt(6) != 'Z') { michael@0: pos.setErrorIndex(6); michael@0: return null; michael@0: } michael@0: michael@0: final int hour = Integer.parseInt(source.substring(0, 2)); michael@0: final int minute = Integer.parseInt(source.substring(2, 4)); michael@0: final int second = Integer.parseInt(source.substring(4, 6)); michael@0: michael@0: final Date d = makeCalendar(isLenient(), getTimeZone(), 1970, 0, 1, hour, minute, second).getTime(); michael@0: pos.setIndex(6); michael@0: return d; michael@0: } catch (Exception e) { michael@0: return null; michael@0: } michael@0: } michael@0: } michael@0: michael@0: private static java.util.Calendar makeCalendar(boolean lenient, java.util.TimeZone timeZone, int year, michael@0: int zeroBasedMonth, int day, int hour, int minutes, int seconds) { michael@0: final java.util.Calendar cal = new GregorianCalendar(timeZone); michael@0: cal.setLenient(lenient); michael@0: cal.set(year, zeroBasedMonth, day, hour, minutes, seconds); michael@0: cal.set(java.util.Calendar.MILLISECOND, 0); michael@0: return cal; michael@0: } michael@0: michael@0: private static java.util.Calendar makeCalendar(boolean lenient, TimeZone timeZone, int year, int month, int day) { michael@0: return makeCalendar(lenient, timeZone, year, month, day, 0, 0, 0); michael@0: } michael@0: michael@0: private static void appendPadded(StringBuffer toAppendTo, int value, int fieldWidth) { michael@0: final String s = Integer.toString(value); michael@0: final int max = fieldWidth - s.length(); michael@0: for (int i = 0; i < max; i++) { michael@0: toAppendTo.append("0"); michael@0: } michael@0: toAppendTo.append(s); michael@0: } michael@0: michael@0: }