Index: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/BufferedStringValue.java =================================================================== --- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/BufferedStringValue.java (revision 629712) +++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/BufferedStringValue.java (working copy) @@ -18,6 +18,7 @@ import org.apache.jackrabbit.core.value.InternalValue; import org.apache.jackrabbit.util.Base64; +import org.apache.jackrabbit.util.ISO8601; import org.apache.jackrabbit.util.TransientFileFactory; import org.apache.jackrabbit.value.ValueHelper; import org.apache.jackrabbit.value.ValueFactoryImpl; @@ -39,6 +40,7 @@ import java.io.Reader; import java.io.StringReader; import java.io.Writer; +import java.util.Calendar; /** * BufferedStringValue represents an appendable @@ -260,6 +262,16 @@ reader.close(); } } + } else if (targetType == PropertyType.DATE) { + // try to parse date in relaxed date format + String value = retrieve(); + Calendar cal = ISO8601.relaxedParse(value); + if (cal != null) { + return ValueHelper.deserialize(ISO8601.format(cal), targetType, true, ValueFactoryImpl.getInstance()); + } else { + // allow the format exception to be thrown later on + return ValueHelper.deserialize(value, targetType, true, ValueFactoryImpl.getInstance()); + } } else { // all other types return ValueHelper.deserialize(retrieve(), targetType, true, ValueFactoryImpl.getInstance()); Index: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/StringValue.java =================================================================== --- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/StringValue.java (revision 629712) +++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/StringValue.java (working copy) @@ -18,6 +18,7 @@ import org.apache.jackrabbit.core.value.InternalValue; import org.apache.jackrabbit.util.Base64; +import org.apache.jackrabbit.util.ISO8601; import org.apache.jackrabbit.value.ValueHelper; import org.apache.jackrabbit.value.ValueFactoryImpl; import org.apache.jackrabbit.spi.commons.conversion.NamePathResolver; @@ -28,6 +29,7 @@ import javax.jcr.ValueFormatException; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.util.Calendar; /** * StringValue represents an immutable serialized value. @@ -68,6 +70,15 @@ return ival.toJCRValue(resolver); } else if (type == PropertyType.BINARY) { return ValueHelper.deserialize(value, type, false, ValueFactoryImpl.getInstance()); + } else if (type == PropertyType.DATE) { + // try to parse date in relaxed date format + Calendar cal = ISO8601.relaxedParse(value); + if (cal != null) { + return ValueHelper.deserialize(ISO8601.format(cal), type, true, ValueFactoryImpl.getInstance()); + } else { + // allow the format exception to be thrown later on + return ValueHelper.deserialize(value, type, true, ValueFactoryImpl.getInstance()); + } } else { // all other types return ValueHelper.deserialize(value, type, true, ValueFactoryImpl.getInstance()); Index: jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/util/ISO8601.java =================================================================== --- jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/util/ISO8601.java (revision 629712) +++ jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/util/ISO8601.java (working copy) @@ -19,6 +19,8 @@ import java.text.DecimalFormat; import java.util.Calendar; import java.util.GregorianCalendar; +import java.util.NoSuchElementException; +import java.util.StringTokenizer; import java.util.TimeZone; /** @@ -55,7 +57,7 @@ private static final DecimalFormat XXXX_FORMAT = new DecimalFormat("0000"); /** - * Parses an ISO8601-compliant date/time string. + * Parses an ISO8601-compliant date/time string that strictly follows JCR specification. * * @param text the date/time string to be parsed * @return a Calendar, or null if the input could @@ -204,7 +206,198 @@ return cal; } + /** + * Parses an ISO8601-compliant date/time string that follows xs:date and xs:dateTime types specification. + * + * @param text the date/time string to be parsed + * @return a Calendar, or null if the input could not be parsed + * @throws IllegalArgumentException if a null argument is passed + */ + public static Calendar relaxedParse(String text) { + + if (text == null) { + throw new IllegalArgumentException("argument can not be null"); + } + + StringTokenizer st = new StringTokenizer(text, "-T:.+Z", true); + + Calendar calendar = new GregorianCalendar(TimeZone.getTimeZone("UTC")); + calendar.clear(); + calendar.setLenient(false); + + if (st.hasMoreElements()) { + + try { + + // year (YYYY) and sign + String token = st.nextToken(); + + int year; + char sign = '+'; + if (token.matches("[+-]")) { + if (st.hasMoreTokens()) { + if (token.equals("-")) { + sign = '-'; + } + year = Integer.parseInt(st.nextToken()); + } else { + return null; + } + } else { + year = Integer.parseInt(token); + } + + if (sign == '-' || year == 0) { + calendar.set(Calendar.YEAR, year + 1); + calendar.set(Calendar.ERA, GregorianCalendar.BC); + } else { + calendar.set(Calendar.YEAR, year); + calendar.set(Calendar.ERA, GregorianCalendar.AD); + } + + // month (MM) + if (checkToken(st, "-") && (st.hasMoreTokens())) { + int month = Integer.parseInt(st.nextToken()) - 1; + calendar.set(Calendar.MONTH, month); + } else { + return null; + } + + // day + if (checkToken(st, "-") && (st.hasMoreTokens())) { + int day = Integer.parseInt(st.nextToken()); + calendar.set(Calendar.DAY_OF_MONTH, day); + } else { + return null; + } + + // optional time part + if (st.hasMoreTokens()) { + + // hour + if (checkToken(st, "T") && (st.hasMoreTokens())) { + int hour = Integer.parseInt(st.nextToken()); + calendar.set(Calendar.HOUR_OF_DAY, hour); + } else { + return null; + } + // minutes + if (checkToken(st, ":") && (st.hasMoreTokens())) { + int minutes = Integer.parseInt(st.nextToken()); + calendar.set(Calendar.MINUTE, minutes); + } else { + return null; + } + + // seconds + if (checkToken(st, ":") && (st.hasMoreTokens())) { + int seconds = Integer.parseInt(st.nextToken()); + calendar.set(Calendar.SECOND, seconds); + calendar.set(Calendar.MILLISECOND, 0); + } else { + return null; + } + + // optional parts (miliseconds, time zone) + if (st.hasMoreTokens()) { + + token = st.nextToken(); + + // miliseconds + if (token.equals(".")) { + if (st.hasMoreTokens()) { + int milis = Math.round(Float.parseFloat("0." + st.nextToken()) * 1000); + calendar.set(Calendar.MILLISECOND, milis); + if (st.hasMoreTokens()) { + token = st.nextToken(); + } else { + token = null; + } + } else { + return null; + } + } + + // GMT time zone + if ((token != null) && token.equals("Z")) { + calendar.setTimeZone(TimeZone.getTimeZone("GMT")); + token = null; + } + + // custom time zone + if ((token != null) && token.matches("[+-]")) { + if (st.hasMoreTokens()) { + String hourToken = st.nextToken(); + if (checkToken(st, ":") && (st.hasMoreTokens())) { + String minuteToken = st.nextToken(); + String tzID = "GMT" + token + hourToken + ":" + minuteToken; + TimeZone tz = TimeZone.getTimeZone(tzID); + // verify id of returned time zone (getTimeZone defaults to "GMT") + if (tz.getID().equals(tzID)) { + calendar.setTimeZone(tz); + } else { + // invalid time zone + return null; + } + token = null; + } else { + return null; + } + } else { + return null; + } + } + + if ((token != null) || st.hasMoreTokens()) { + return null; + } + + } + + } + + } catch (IndexOutOfBoundsException e) { + return null; + } catch (NumberFormatException e) { + return null; + } catch (NoSuchElementException ex) { + return null; + } + + } else { + return null; + } + + try { + // the following call will trigger an IllegalArgumentException if any of + // the set values are illegal or out of range + calendar.getTime(); + } catch (IllegalArgumentException e) { + return null; + } + + return calendar; + + } + + /** + * Checks if given token is present in tokenizer. + * + * @param st tokenizer object. + * @param token token to check. + * @return true if given token is available, false otherwise. + */ + private static boolean checkToken(StringTokenizer st, String token) { + try { + return st.nextToken().equals(token); + } catch (NoSuchElementException ex) { + return false; + } + } + + + /** * Formats a Calendar value into an ISO8601-compliant * date/time string. *