From 7e01c66374863859f54b1140b25d0b8102555b7e Mon Sep 17 00:00:00 2001 From: Henning Staib Date: Sun, 15 Aug 2010 13:20:48 +0000 Subject: [PATCH] improved Delay Information Parser (fixes SMACK-243) git-svn-id: http://svn.igniterealtime.org/svn/repos/smack/trunk@11823 b35dd754-fafc-0310-a699-88a17e54d16e --- .../jivesoftware/smack/util/StringUtils.java | 44 ++++ .../jivesoftware/smackx/packet/DelayInfo.java | 13 +- .../smackx/packet/DelayInformation.java | 37 +-- .../smackx/packet/StreamInitiation.java | 6 +- .../smackx/provider/DelayInfoProvider.java | 5 +- .../provider/DelayInformationProvider.java | 181 +++++++++++-- .../provider/StreamInitiationProvider.java | 18 +- .../smackx/provider/DelayInformationTest.java | 248 ++++++++++++++++++ 8 files changed, 496 insertions(+), 56 deletions(-) create mode 100644 test-unit/org/jivesoftware/smackx/provider/DelayInformationTest.java diff --git a/source/org/jivesoftware/smack/util/StringUtils.java b/source/org/jivesoftware/smack/util/StringUtils.java index 628f4b57e..2c0bc3eec 100644 --- a/source/org/jivesoftware/smack/util/StringUtils.java +++ b/source/org/jivesoftware/smack/util/StringUtils.java @@ -23,19 +23,63 @@ package org.jivesoftware.smack.util; import java.io.UnsupportedEncodingException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; import java.util.Random; +import java.util.TimeZone; /** * A collection of utility methods for String objects. */ public class StringUtils { + /** + * Date format as defined in XEP-0082 - XMPP Date and Time Profiles. The time zone is set to + * UTC. + *

+ * Date formats are not synchronized. Since multiple threads access the format concurrently, it + * must be synchronized externally or you can use the convenience methods + * {@link #parseXEP0082Date(String)} and {@link #formatXEP0082Date(Date)}. + */ + public static final DateFormat XEP_0082_UTC_FORMAT = new SimpleDateFormat( + "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); + static { + XEP_0082_UTC_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC")); + } + private static final char[] QUOTE_ENCODE = """.toCharArray(); private static final char[] APOS_ENCODE = "'".toCharArray(); private static final char[] AMP_ENCODE = "&".toCharArray(); private static final char[] LT_ENCODE = "<".toCharArray(); private static final char[] GT_ENCODE = ">".toCharArray(); + /** + * Parses the given date string in the XEP-0082 - XMPP Date and Time Profiles format. + * + * @param dateString the date string to parse + * @return the parsed Date + * @throws ParseException if the specified string cannot be parsed + */ + public static Date parseXEP0082Date(String dateString) throws ParseException { + synchronized (XEP_0082_UTC_FORMAT) { + return XEP_0082_UTC_FORMAT.parse(dateString); + } + } + + /** + * Formats a Date into a XEP-0082 - XMPP Date and Time Profiles string. + * + * @param date the time value to be formatted into a time string + * @return the formatted time string in XEP-0082 format + */ + public static String formatXEP0082Date(Date date) { + synchronized (XEP_0082_UTC_FORMAT) { + return XEP_0082_UTC_FORMAT.format(date); + } + } + /** * Returns the name portion of a XMPP address. For example, for the * address "matt@jivesoftware.com/Smack", "matt" would be returned. If no diff --git a/source/org/jivesoftware/smackx/packet/DelayInfo.java b/source/org/jivesoftware/smackx/packet/DelayInfo.java index 6026db03e..f5ba78fa1 100644 --- a/source/org/jivesoftware/smackx/packet/DelayInfo.java +++ b/source/org/jivesoftware/smackx/packet/DelayInfo.java @@ -15,12 +15,12 @@ package org.jivesoftware.smackx.packet; import java.util.Date; -import org.jivesoftware.smackx.packet.DelayInformation; +import org.jivesoftware.smack.util.StringUtils; /** * A decorator for the {@link DelayInformation} class to transparently support * both the new Delay Delivery specification XEP-0203 and - * the old one XEP-0091. + * the old one XEP-0091. * * Existing code can be backward compatible. * @@ -28,8 +28,13 @@ import org.jivesoftware.smackx.packet.DelayInformation; */ public class DelayInfo extends DelayInformation { + DelayInformation wrappedInfo; + /** + * Creates a new instance with given delay information. + * @param delay the delay information + */ public DelayInfo(DelayInformation delay) { super(delay.getStamp()); @@ -84,9 +89,7 @@ public class DelayInfo extends DelayInformation buf.append("<").append(getElementName()).append(" xmlns=\"").append(getNamespace()).append( "\""); buf.append(" stamp=\""); - synchronized (NEW_UTC_FORMAT) { - buf.append(NEW_UTC_FORMAT.format(getStamp())); - } + buf.append(StringUtils.formatXEP0082Date(getStamp())); buf.append("\""); if (getFrom() != null && getFrom().length() > 0) { buf.append(" from=\"").append(getFrom()).append("\""); diff --git a/source/org/jivesoftware/smackx/packet/DelayInformation.java b/source/org/jivesoftware/smackx/packet/DelayInformation.java index b03f5cd2c..b9ab48518 100644 --- a/source/org/jivesoftware/smackx/packet/DelayInformation.java +++ b/source/org/jivesoftware/smackx/packet/DelayInformation.java @@ -20,36 +20,38 @@ package org.jivesoftware.smackx.packet; -import org.jivesoftware.smack.packet.PacketExtension; - +import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; import java.util.TimeZone; +import org.jivesoftware.smack.packet.PacketExtension; + /** * Represents timestamp information about data stored for later delivery. A DelayInformation will * always includes the timestamp when the packet was originally sent and may include more * information such as the JID of the entity that originally sent the packet as well as the reason - * for the dealy.

+ * for the delay.

* - * For more information see JEP-91. + * For more information see XEP-0091 + * and XEP-0203. * * @author Gaston Dombiak */ public class DelayInformation implements PacketExtension { - public static SimpleDateFormat UTC_FORMAT = new SimpleDateFormat("yyyyMMdd'T'HH:mm:ss"); /** - * New date format based on JEP-82 that some clients may use when sending delayed dates. - * JEP-91 is using a SHOULD other servers or clients may be using this format instead of the - * old UTC format. + * Date format according to the obsolete XEP-0091 specification. + * XEP-0091 recommends to use this old format for date-time instead of + * the one specified in XEP-0082. + *

+ * Date formats are not synchronized. Since multiple threads access the format concurrently, + * it must be synchronized externally. */ - public static SimpleDateFormat NEW_UTC_FORMAT = - new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); - + public static final DateFormat XEP_0091_UTC_FORMAT = new SimpleDateFormat( + "yyyyMMdd'T'HH:mm:ss"); static { - UTC_FORMAT.setTimeZone(TimeZone.getTimeZone("GMT+0")); - NEW_UTC_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC")); + XEP_0091_UTC_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC")); } private Date stamp; @@ -58,6 +60,7 @@ public class DelayInformation implements PacketExtension { /** * Creates a new instance with the specified timestamp. + * @param stamp the timestamp */ public DelayInformation(Date stamp) { super(); @@ -86,10 +89,10 @@ public class DelayInformation implements PacketExtension { } /** - * Returns the timstamp when the packet was originally sent. The returned Date is + * Returns the timestamp when the packet was originally sent. The returned Date is * be understood as UTC. * - * @return the timstamp when the packet was originally sent. + * @return the timestamp when the packet was originally sent. */ public Date getStamp() { return stamp; @@ -128,8 +131,8 @@ public class DelayInformation implements PacketExtension { buf.append("<").append(getElementName()).append(" xmlns=\"").append(getNamespace()).append( "\""); buf.append(" stamp=\""); - synchronized (UTC_FORMAT) { - buf.append(UTC_FORMAT.format(stamp)); + synchronized (XEP_0091_UTC_FORMAT) { + buf.append(XEP_0091_UTC_FORMAT.format(stamp)); } buf.append("\""); if (from != null && from.length() > 0) { diff --git a/source/org/jivesoftware/smackx/packet/StreamInitiation.java b/source/org/jivesoftware/smackx/packet/StreamInitiation.java index 876f3718f..10c1452c6 100644 --- a/source/org/jivesoftware/smackx/packet/StreamInitiation.java +++ b/source/org/jivesoftware/smackx/packet/StreamInitiation.java @@ -19,12 +19,12 @@ */ package org.jivesoftware.smackx.packet; +import java.util.Date; + import org.jivesoftware.smack.packet.IQ; import org.jivesoftware.smack.packet.PacketExtension; import org.jivesoftware.smack.util.StringUtils; -import java.util.Date; - /** * The process by which two entities initiate a stream. * @@ -347,7 +347,7 @@ public class StreamInitiation extends IQ { } if (getDate() != null) { - buffer.append("date=\"").append(DelayInformation.UTC_FORMAT.format(date)).append("\" "); + buffer.append("date=\"").append(StringUtils.formatXEP0082Date(date)).append("\" "); } if (getHash() != null) { diff --git a/source/org/jivesoftware/smackx/provider/DelayInfoProvider.java b/source/org/jivesoftware/smackx/provider/DelayInfoProvider.java index a5bc06f39..9d1dcd8f4 100644 --- a/source/org/jivesoftware/smackx/provider/DelayInfoProvider.java +++ b/source/org/jivesoftware/smackx/provider/DelayInfoProvider.java @@ -21,8 +21,9 @@ import org.xmlpull.v1.XmlPullParser; /** * This provider simply creates a {@link DelayInfo} decorator for the {@link DelayInformation} that - * is returned by the superclass. This allows the new code using Delay Information XEP-0203 to be backward compatible - * with XEP-0091. + * is returned by the superclass. This allows the new code using + * Delay Information XEP-0203 to be + * backward compatible with XEP-0091. * *

This provider must be registered in the smack.properties file for the element * delay with namespace urn:xmpp:delay

diff --git a/source/org/jivesoftware/smackx/provider/DelayInformationProvider.java b/source/org/jivesoftware/smackx/provider/DelayInformationProvider.java index 995b9eff8..d20c22d53 100644 --- a/source/org/jivesoftware/smackx/provider/DelayInformationProvider.java +++ b/source/org/jivesoftware/smackx/provider/DelayInformationProvider.java @@ -20,56 +20,185 @@ package org.jivesoftware.smackx.provider; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.TimeZone; + import org.jivesoftware.smack.packet.PacketExtension; import org.jivesoftware.smack.provider.PacketExtensionProvider; +import org.jivesoftware.smack.util.StringUtils; import org.jivesoftware.smackx.packet.DelayInformation; import org.xmlpull.v1.XmlPullParser; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.TimeZone; - /** * The DelayInformationProvider parses DelayInformation packets. - * + * * @author Gaston Dombiak + * @author Henning Staib */ public class DelayInformationProvider implements PacketExtensionProvider { + /* + * Date format used to parse dates in the XEP-0091 format but missing leading + * zeros for month and day. + */ + private static final SimpleDateFormat XEP_0091_UTC_FALLBACK_FORMAT = new SimpleDateFormat( + "yyyyMd'T'HH:mm:ss"); + static { + XEP_0091_UTC_FALLBACK_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC")); + } + + /* + * Date format used to parse dates in the XEP-0082 format but missing milliseconds. + */ + private static final SimpleDateFormat XEP_0082_UTC_FORMAT_WITHOUT_MILLIS = new SimpleDateFormat( + "yyyy-MM-dd'T'HH:mm:ss'Z'"); + static { + XEP_0082_UTC_FORMAT_WITHOUT_MILLIS.setTimeZone(TimeZone.getTimeZone("UTC")); + } + + /* + * Maps a regular expression for a date format to the date format parser. + */ + private static Map formats = new HashMap(); + static { + formats.put("^\\d+T\\d+:\\d+:\\d+$", DelayInformation.XEP_0091_UTC_FORMAT); + formats.put("^\\d+-\\d+-\\d+T\\d+:\\d+:\\d+\\.\\d+Z$", StringUtils.XEP_0082_UTC_FORMAT); + formats.put("^\\d+-\\d+-\\d+T\\d+:\\d+:\\d+Z$", XEP_0082_UTC_FORMAT_WITHOUT_MILLIS); + } + /** - * Creates a new DeliveryInformationProvider. - * ProviderManager requires that every PacketExtensionProvider has a public, no-argument - * constructor + * Creates a new DeliveryInformationProvider. ProviderManager requires that + * every PacketExtensionProvider has a public, no-argument constructor */ public DelayInformationProvider() { } public PacketExtension parseExtension(XmlPullParser parser) throws Exception { + String stampString = (parser.getAttributeValue("", "stamp")); Date stamp = null; - try { - synchronized (DelayInformation.UTC_FORMAT) { - stamp = DelayInformation.UTC_FORMAT.parse(parser.getAttributeValue("", "stamp")); - } - } catch (ParseException e) { - // Try again but assuming that the date follows JEP-82 format - // (Jabber Date and Time Profiles) - try { - synchronized (DelayInformation.NEW_UTC_FORMAT) { - stamp = DelayInformation.NEW_UTC_FORMAT - .parse(parser.getAttributeValue("", "stamp")); + DateFormat format = null; + + for (String regexp : formats.keySet()) { + if (stampString.matches(regexp)) { + try { + format = formats.get(regexp); + synchronized (format) { + stamp = format.parse(stampString); + } } - } catch (ParseException e1) { - // Last attempt. Try parsing the date assuming that it does not include milliseconds - SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); - formatter.setTimeZone(TimeZone.getTimeZone("UTC")); - stamp = formatter.parse(parser.getAttributeValue("", "stamp")); + catch (ParseException e) { + // do nothing, format is still set + } + + // break because only one regexp can match + break; } } + + /* + * if date is in XEP-0091 format handle ambiguous dates missing the + * leading zero in month and day + */ + if (format == DelayInformation.XEP_0091_UTC_FORMAT + && stampString.split("T")[0].length() < 8) { + stamp = handleDateWithMissingLeadingZeros(stampString); + } + + /* + * if date could not be parsed but XML is valid, don't shutdown + * connection by throwing an exception instead set timestamp to current + * time + */ + if (stamp == null) { + stamp = new Date(); + } + DelayInformation delayInformation = new DelayInformation(stamp); delayInformation.setFrom(parser.getAttributeValue("", "from")); - delayInformation.setReason(parser.nextText()); + String reason = parser.nextText(); + + /* + * parser.nextText() returns empty string if there is no reason. + * DelayInformation API specifies that null should be returned in that + * case. + */ + reason = "".equals(reason) ? null : reason; + delayInformation.setReason(reason); + return delayInformation; } + /** + * Parses the given date string in different ways and returns the date that + * lies in the past and/or is nearest to the current date-time. + * + * @param stampString date in string representation + * @return the parsed date + */ + private Date handleDateWithMissingLeadingZeros(String stampString) { + Calendar now = new GregorianCalendar(); + Calendar xep91 = null; + Calendar xep91Fallback = null; + + xep91 = parseXEP91Date(stampString, DelayInformation.XEP_0091_UTC_FORMAT); + xep91Fallback = parseXEP91Date(stampString, XEP_0091_UTC_FALLBACK_FORMAT); + + List dates = filterDatesBefore(now, xep91, xep91Fallback); + + if (!dates.isEmpty()) { + return determineNearestDate(now, dates).getTime(); + } + return null; + } + + private Calendar parseXEP91Date(String stampString, DateFormat dateFormat) { + try { + synchronized (dateFormat) { + dateFormat.parse(stampString); + return dateFormat.getCalendar(); + } + } + catch (ParseException e) { + return null; + } + } + + private List filterDatesBefore(Calendar now, Calendar... dates) { + List result = new ArrayList(); + + for (Calendar calendar : dates) { + if (calendar != null && calendar.before(now)) { + result.add(calendar); + } + } + + return result; + } + + private Calendar determineNearestDate(final Calendar now, List dates) { + + Collections.sort(dates, new Comparator() { + + public int compare(Calendar o1, Calendar o2) { + Long diff1 = new Long(now.getTimeInMillis() - o1.getTimeInMillis()); + Long diff2 = new Long(now.getTimeInMillis() - o2.getTimeInMillis()); + return diff1.compareTo(diff2); + } + + }); + + return dates.get(0); + } + } diff --git a/source/org/jivesoftware/smackx/provider/StreamInitiationProvider.java b/source/org/jivesoftware/smackx/provider/StreamInitiationProvider.java index 4a7c1adba..c48d9b285 100644 --- a/source/org/jivesoftware/smackx/provider/StreamInitiationProvider.java +++ b/source/org/jivesoftware/smackx/provider/StreamInitiationProvider.java @@ -19,10 +19,13 @@ */ package org.jivesoftware.smackx.provider; +import java.text.ParseException; +import java.util.Date; + import org.jivesoftware.smack.packet.IQ; import org.jivesoftware.smack.provider.IQProvider; +import org.jivesoftware.smack.util.StringUtils; import org.jivesoftware.smackx.packet.DataForm; -import org.jivesoftware.smackx.packet.DelayInformation; import org.jivesoftware.smackx.packet.StreamInitiation; import org.jivesoftware.smackx.packet.StreamInitiation.File; import org.xmlpull.v1.XmlPullParser; @@ -90,10 +93,19 @@ public class StreamInitiationProvider implements IQProvider { e.printStackTrace(); } } + + Date fileDate = new Date(); + if (date != null) { + try { + fileDate = StringUtils.parseXEP0082Date(date); + } catch (ParseException e) { + // couldn't parse date, use current date-time + } + } + File file = new File(name, fileSize); file.setHash(hash); - if (date != null) - file.setDate(DelayInformation.UTC_FORMAT.parse(date)); + file.setDate(fileDate); file.setDesc(desc); file.setRanged(isRanged); initiation.setFile(file); diff --git a/test-unit/org/jivesoftware/smackx/provider/DelayInformationTest.java b/test-unit/org/jivesoftware/smackx/provider/DelayInformationTest.java new file mode 100644 index 000000000..44a82b61f --- /dev/null +++ b/test-unit/org/jivesoftware/smackx/provider/DelayInformationTest.java @@ -0,0 +1,248 @@ +/** + * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jivesoftware.smackx.provider; + +import static org.junit.Assert.*; + +import java.io.IOException; +import java.io.StringReader; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.Properties; +import java.util.TimeZone; + +import org.jivesoftware.smack.util.StringUtils; +import org.jivesoftware.smackx.packet.DelayInfo; +import org.jivesoftware.smackx.packet.DelayInformation; +import org.junit.Test; +import org.xmlpull.mxp1.MXParser; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import com.jamesmurty.utils.XMLBuilder; + +public class DelayInformationTest { + + private static Properties outputProperties = new Properties(); + static { + outputProperties.put(javax.xml.transform.OutputKeys.OMIT_XML_DECLARATION, "yes"); + } + + @Test + public void delayInformationTest() throws Exception { + DelayInformationProvider p = new DelayInformationProvider(); + DelayInformation delayInfo; + XmlPullParser parser; + String control; + GregorianCalendar calendar = new GregorianCalendar(2002, 9 - 1, 10, 23, 8, 25); + calendar.setTimeZone(TimeZone.getTimeZone("UTC")); + Date date = calendar.getTime(); + + control = XMLBuilder.create("x") + .a("xmlns", "jabber:x:delay") + .a("from", "capulet.com") + .a("stamp", "2002-09-10T23:08:25Z") + .t("Offline Storage") + .asString(outputProperties); + + parser = getParser(control, "x"); + delayInfo = (DelayInformation) p.parseExtension(parser); + + assertEquals("capulet.com", delayInfo.getFrom()); + assertEquals(date, delayInfo.getStamp()); + assertEquals("Offline Storage", delayInfo.getReason()); + + assertEquals(XmlPullParser.END_TAG, parser.getEventType()); + assertEquals("x", parser.getName()); + + control = XMLBuilder.create("x") + .a("xmlns", "jabber:x:delay") + .a("from", "capulet.com") + .a("stamp", "2002-09-10T23:08:25Z") + .asString(outputProperties); + + parser = getParser(control, "x"); + delayInfo = (DelayInformation) p.parseExtension(parser); + + assertEquals("capulet.com", delayInfo.getFrom()); + assertEquals(date, delayInfo.getStamp()); + assertNull(delayInfo.getReason()); + + assertEquals(XmlPullParser.END_TAG, parser.getEventType()); + assertEquals("x", parser.getName()); + + } + + @Test + public void delayInfoTest() throws Exception { + DelayInformationProvider p = new DelayInfoProvider(); + DelayInfo delayInfo; + XmlPullParser parser; + String control; + GregorianCalendar calendar = new GregorianCalendar(2002, 9 - 1, 10, 23, 8, 25); + calendar.setTimeZone(TimeZone.getTimeZone("UTC")); + Date date = calendar.getTime(); + + control = XMLBuilder.create("delay") + .a("xmlns", "urn:xmpp:delay") + .a("from", "capulet.com") + .a("stamp", "2002-09-10T23:08:25Z") + .t("Offline Storage") + .asString(outputProperties); + + parser = getParser(control, "delay"); + delayInfo = (DelayInfo) p.parseExtension(parser); + + assertEquals("capulet.com", delayInfo.getFrom()); + assertEquals(date, delayInfo.getStamp()); + assertEquals("Offline Storage", delayInfo.getReason()); + + assertEquals(XmlPullParser.END_TAG, parser.getEventType()); + assertEquals("delay", parser.getName()); + + control = XMLBuilder.create("delay") + .a("xmlns", "urn:xmpp:delay") + .a("from", "capulet.com") + .a("stamp", "2002-09-10T23:08:25Z") + .asString(outputProperties); + + parser = getParser(control, "delay"); + delayInfo = (DelayInfo) p.parseExtension(parser); + + assertEquals("capulet.com", delayInfo.getFrom()); + assertEquals(date, delayInfo.getStamp()); + assertNull(delayInfo.getReason()); + + assertEquals(XmlPullParser.END_TAG, parser.getEventType()); + assertEquals("delay", parser.getName()); + + } + + @Test + public void dateFormatsTest() throws Exception { + DelayInformationProvider p = new DelayInfoProvider(); + DelayInfo delayInfo; + String control; + GregorianCalendar calendar = new GregorianCalendar(2002, 9 - 1, 10, 23, 8, 25); + calendar.setTimeZone(TimeZone.getTimeZone("UTC")); + + // XEP-0082 date format + control = XMLBuilder.create("delay") + .a("xmlns", "urn:xmpp:delay") + .a("from", "capulet.com") + .a("stamp", "2002-09-10T23:08:25.12Z") + .asString(outputProperties); + + delayInfo = (DelayInfo) p.parseExtension(getParser(control, "delay")); + + GregorianCalendar cal = (GregorianCalendar) calendar.clone(); + cal.add(Calendar.MILLISECOND, 12); + assertEquals(cal.getTime(), delayInfo.getStamp()); + + // XEP-0082 date format without milliseconds + control = XMLBuilder.create("delay") + .a("xmlns", "urn:xmpp:delay") + .a("from", "capulet.com") + .a("stamp", "2002-09-10T23:08:25Z") + .asString(outputProperties); + + delayInfo = (DelayInfo) p.parseExtension(getParser(control, "delay")); + + assertEquals(calendar.getTime(), delayInfo.getStamp()); + + // XEP-0082 date format without milliseconds and leading 0 in month + control = XMLBuilder.create("delay") + .a("xmlns", "urn:xmpp:delay") + .a("from", "capulet.com") + .a("stamp", "2002-9-10T23:08:25Z") + .asString(outputProperties); + + delayInfo = (DelayInfo) p.parseExtension(getParser(control, "delay")); + + assertEquals(calendar.getTime(), delayInfo.getStamp()); + + // XEP-0091 date format + control = XMLBuilder.create("delay") + .a("xmlns", "urn:xmpp:delay") + .a("from", "capulet.com") + .a("stamp", "20020910T23:08:25") + .asString(outputProperties); + + delayInfo = (DelayInfo) p.parseExtension(getParser(control, "delay")); + + assertEquals(calendar.getTime(), delayInfo.getStamp()); + + // XEP-0091 date format without leading 0 in month + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMd'T'HH:mm:ss"); + dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); + GregorianCalendar dateInPast = new GregorianCalendar(); + if (dateInPast.get(Calendar.MONTH) >= 10) { + dateInPast.set(Calendar.MONTH, dateInPast.get(Calendar.MONTH) - 3); + } + dateInPast.add(Calendar.DAY_OF_MONTH, -3); + dateInPast.set(Calendar.MILLISECOND, 0); + + control = XMLBuilder.create("delay") + .a("xmlns", "urn:xmpp:delay") + .a("from", "capulet.com") + .a("stamp", dateFormat.format(dateInPast.getTime())) + .asString(outputProperties); + + delayInfo = (DelayInfo) p.parseExtension(getParser(control, "delay")); + + assertEquals(dateInPast.getTime(), delayInfo.getStamp()); + + // XEP-0091 date format from SMACK-243 + control = XMLBuilder.create("delay") + .a("xmlns", "urn:xmpp:delay") + .a("from", "capulet.com") + .a("stamp", "200868T09:16:20") + .asString(outputProperties); + + delayInfo = (DelayInfo) p.parseExtension(getParser(control, "delay")); + Date controlDate = StringUtils.parseXEP0082Date("2008-06-08T09:16:20.0Z"); + + assertEquals(controlDate, delayInfo.getStamp()); + + // invalid date format + control = XMLBuilder.create("delay") + .a("xmlns", "urn:xmpp:delay") + .a("from", "capulet.com") + .a("stamp", "yesterday") + .asString(outputProperties); + + delayInfo = (DelayInfo) p.parseExtension(getParser(control, "delay")); + + assertNotNull(delayInfo.getStamp()); + + } + + private XmlPullParser getParser(String control, String startTag) + throws XmlPullParserException, IOException { + XmlPullParser parser = new MXParser(); + parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); + parser.setInput(new StringReader(control)); + + while (true) { + if (parser.next() == XmlPullParser.START_TAG + && parser.getName().equals(startTag)) { + break; + } + } + return parser; + } + +}