From 585e20e93e60f543ed5848d177093acd6580b275 Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Mon, 3 Mar 2014 09:44:32 +0100 Subject: [PATCH] Rework XMPP Date/Time related code - Fix "packet.Time is not thread-safe" (SMACK-543) - Update packet.Time to XEP-0202 Add SDM.supportsFeature(), since this is a pattern that repeats over and over again in Smack. Also add abstract Manager class, that takes care of the weak reference to Connection, as this is also a repeating pattern in Smack. --- .../java/org/jivesoftware/smack/Manager.java | 32 ++ .../smack/provider/ProviderManager.java | 2 +- .../smack/util/DateFormatType.java | 62 ---- .../smack/util/PacketParserUtils.java | 8 +- .../jivesoftware/smack/util/StringUtils.java | 268 --------------- .../jivesoftware/smack/util/XmppDateTime.java | 325 ++++++++++++++++++ .../smack/util/StringUtilsTest.java | 226 ------------ .../smack/util/XmppDateTimeTest.java | 232 +++++++++++++ .../smackx/delay/packet/DelayInfo.java | 4 +- .../provider/DelayInformationProvider.java | 4 +- .../smackx/disco/ServiceDiscoveryManager.java | 13 + .../smackx/pubsub/SubscribeForm.java | 6 +- .../smackx/si/packet/StreamInitiation.java | 3 +- .../si/provider/StreamInitiationProvider.java | 4 +- .../smackx/time/EntityTimeManager.java | 111 ++++++ .../jivesoftware/smackx/time/packet/Time.java | 117 ++----- .../extensions.providers | 36 +- .../org.jivesoftware.smackx/extensions.xml | 1 + .../jivesoftware/smackx/InitExtensions.java | 26 ++ .../delay/provider/DelayInformationTest.java | 4 +- .../smackx/time/packet/TimeTest.java | 98 ++++++ 21 files changed, 904 insertions(+), 678 deletions(-) create mode 100644 core/src/main/java/org/jivesoftware/smack/Manager.java delete mode 100644 core/src/main/java/org/jivesoftware/smack/util/DateFormatType.java create mode 100644 core/src/main/java/org/jivesoftware/smack/util/XmppDateTime.java create mode 100644 core/src/test/java/org/jivesoftware/smack/util/XmppDateTimeTest.java create mode 100644 extensions/src/main/java/org/jivesoftware/smackx/time/EntityTimeManager.java create mode 100644 extensions/src/test/java/org/jivesoftware/smackx/InitExtensions.java create mode 100644 extensions/src/test/java/org/jivesoftware/smackx/time/packet/TimeTest.java diff --git a/core/src/main/java/org/jivesoftware/smack/Manager.java b/core/src/main/java/org/jivesoftware/smack/Manager.java new file mode 100644 index 000000000..1a9e98b49 --- /dev/null +++ b/core/src/main/java/org/jivesoftware/smack/Manager.java @@ -0,0 +1,32 @@ +/** + * + * Copyright 2014 Florian Schmaus + * + * 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.smack; + +import java.lang.ref.WeakReference; + +public abstract class Manager { + + final WeakReference weakConnection; + + public Manager(Connection connection) { + weakConnection = new WeakReference(connection); + } + + protected final Connection connection() { + return weakConnection.get(); + } +} diff --git a/core/src/main/java/org/jivesoftware/smack/provider/ProviderManager.java b/core/src/main/java/org/jivesoftware/smack/provider/ProviderManager.java index c70076e56..b3b517d8a 100644 --- a/core/src/main/java/org/jivesoftware/smack/provider/ProviderManager.java +++ b/core/src/main/java/org/jivesoftware/smack/provider/ProviderManager.java @@ -196,7 +196,7 @@ public final class ProviderManager { IQ.class.isAssignableFrom((Class)provider)))) { throw new IllegalArgumentException("Provider must be an IQProvider " + - "or a Class instance."); + "or a Class instance sublcassing IQ."); } String key = getProviderKey(elementName, namespace); iqProviders.put(key, provider); diff --git a/core/src/main/java/org/jivesoftware/smack/util/DateFormatType.java b/core/src/main/java/org/jivesoftware/smack/util/DateFormatType.java deleted file mode 100644 index cae6e491c..000000000 --- a/core/src/main/java/org/jivesoftware/smack/util/DateFormatType.java +++ /dev/null @@ -1,62 +0,0 @@ -/** - * - * Copyright 2013 Robin Collier. - * - * 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.smack.util; - -import java.text.SimpleDateFormat; - -/** - * Defines the various date and time profiles used in XMPP along with their associated formats. - * - * @author Robin Collier - * - */ -public enum DateFormatType { - // @formatter:off - XEP_0082_DATE_PROFILE("yyyy-MM-dd"), - XEP_0082_DATETIME_PROFILE("yyyy-MM-dd'T'HH:mm:ssZ"), - XEP_0082_DATETIME_MILLIS_PROFILE("yyyy-MM-dd'T'HH:mm:ss.SSSZ"), - XEP_0082_TIME_PROFILE("hh:mm:ss"), - XEP_0082_TIME_ZONE_PROFILE("hh:mm:ssZ"), - XEP_0082_TIME_MILLIS_PROFILE("hh:mm:ss.SSS"), - XEP_0082_TIME_MILLIS_ZONE_PROFILE("hh:mm:ss.SSSZ"), - XEP_0091_DATETIME("yyyyMMdd'T'HH:mm:ss"); - // @formatter:on - - private String formatString; - - private DateFormatType(String dateFormat) { - formatString = dateFormat; - } - - /** - * Get the format string as defined in either XEP-0082 or XEP-0091. - * - * @return The defined string format for the date. - */ - public String getFormatString() { - return formatString; - } - - /** - * Create a {@link SimpleDateFormat} object with the format defined by {@link #getFormatString()}. - * - * @return A new date formatter. - */ - public SimpleDateFormat createFormatter() { - return new SimpleDateFormat(getFormatString()); - } -} diff --git a/core/src/main/java/org/jivesoftware/smack/util/PacketParserUtils.java b/core/src/main/java/org/jivesoftware/smack/util/PacketParserUtils.java index 81605a1e8..2b64c4976 100644 --- a/core/src/main/java/org/jivesoftware/smack/util/PacketParserUtils.java +++ b/core/src/main/java/org/jivesoftware/smack/util/PacketParserUtils.java @@ -341,9 +341,9 @@ public class PacketParserUtils { // Decide what to do when an IQ packet was not understood if (iqPacket == null) { if (IQ.Type.GET == type || IQ.Type.SET == type ) { - // If the IQ stanza is of type "get" or "set" containing a child element - // qualified by a namespace it does not understand, then answer an IQ of - // type "error" with code 501 ("feature-not-implemented") + // If the IQ stanza is of type "get" or "set" containing a child element qualified + // by a namespace with no registered Smack provider, then answer an IQ of type + // "error" with code 501 ("feature-not-implemented") iqPacket = new IQ() { @Override public String getChildElementXML() { @@ -848,7 +848,7 @@ public class PacketParserUtils { } } return object; - } + } /** * Decodes a String into an object of the specified type. If the object diff --git a/core/src/main/java/org/jivesoftware/smack/util/StringUtils.java b/core/src/main/java/org/jivesoftware/smack/util/StringUtils.java index 73ad26926..2cb5e9c1e 100644 --- a/core/src/main/java/org/jivesoftware/smack/util/StringUtils.java +++ b/core/src/main/java/org/jivesoftware/smack/util/StringUtils.java @@ -20,94 +20,15 @@ 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.ArrayList; -import java.util.Calendar; -import java.util.Collections; -import java.util.Comparator; -import java.util.Date; -import java.util.List; import java.util.Random; -import java.util.TimeZone; import java.util.logging.Level; import java.util.logging.Logger; -import java.util.regex.Matcher; -import java.util.regex.Pattern; /** * A collection of utility methods for String objects. */ public class StringUtils { private static final Logger LOGGER = Logger.getLogger(StringUtils.class.getName()); - - /** - * 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)}. - * @deprecated This public version will be removed in favor of using the methods defined within this class. - */ - public static final DateFormat XEP_0082_UTC_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); - - /* - * private version to use internally so we don't have to be concerned with thread safety. - */ - private static final DateFormat dateFormatter = DateFormatType.XEP_0082_DATE_PROFILE.createFormatter(); - private static final Pattern datePattern = Pattern.compile("^\\d+-\\d+-\\d+$"); - - private static final DateFormat timeFormatter = DateFormatType.XEP_0082_TIME_MILLIS_ZONE_PROFILE.createFormatter(); - private static final Pattern timePattern = Pattern.compile("^(\\d+:){2}\\d+.\\d+(Z|([+-](\\d+:\\d+)))$"); - private static final DateFormat timeNoZoneFormatter = DateFormatType.XEP_0082_TIME_MILLIS_PROFILE.createFormatter(); - private static final Pattern timeNoZonePattern = Pattern.compile("^(\\d+:){2}\\d+.\\d+$"); - - private static final DateFormat timeNoMillisFormatter = DateFormatType.XEP_0082_TIME_ZONE_PROFILE.createFormatter(); - private static final Pattern timeNoMillisPattern = Pattern.compile("^(\\d+:){2}\\d+(Z|([+-](\\d+:\\d+)))$"); - private static final DateFormat timeNoMillisNoZoneFormatter = DateFormatType.XEP_0082_TIME_PROFILE.createFormatter(); - private static final Pattern timeNoMillisNoZonePattern = Pattern.compile("^(\\d+:){2}\\d+$"); - - private static final DateFormat dateTimeFormatter = DateFormatType.XEP_0082_DATETIME_MILLIS_PROFILE.createFormatter(); - private static final Pattern dateTimePattern = Pattern.compile("^\\d+(-\\d+){2}+T(\\d+:){2}\\d+.\\d+(Z|([+-](\\d+:\\d+)))?$"); - private static final DateFormat dateTimeNoMillisFormatter = DateFormatType.XEP_0082_DATETIME_PROFILE.createFormatter(); - private static final Pattern dateTimeNoMillisPattern = Pattern.compile("^\\d+(-\\d+){2}+T(\\d+:){2}\\d+(Z|([+-](\\d+:\\d+)))?$"); - - private static final DateFormat xep0091Formatter = new SimpleDateFormat("yyyyMMdd'T'HH:mm:ss"); - private static final DateFormat xep0091Date6DigitFormatter = new SimpleDateFormat("yyyyMd'T'HH:mm:ss"); - private static final DateFormat xep0091Date7Digit1MonthFormatter = new SimpleDateFormat("yyyyMdd'T'HH:mm:ss"); - private static final DateFormat xep0091Date7Digit2MonthFormatter = new SimpleDateFormat("yyyyMMd'T'HH:mm:ss"); - private static final Pattern xep0091Pattern = Pattern.compile("^\\d+T\\d+:\\d+:\\d+$"); - - private static final List couplings = new ArrayList(); - - static { - TimeZone utc = TimeZone.getTimeZone("UTC"); - XEP_0082_UTC_FORMAT.setTimeZone(utc); - dateFormatter.setTimeZone(utc); - timeFormatter.setTimeZone(utc); - timeNoZoneFormatter.setTimeZone(utc); - timeNoMillisFormatter.setTimeZone(utc); - timeNoMillisNoZoneFormatter.setTimeZone(utc); - dateTimeFormatter.setTimeZone(utc); - dateTimeNoMillisFormatter.setTimeZone(utc); - - xep0091Formatter.setTimeZone(utc); - xep0091Date6DigitFormatter.setTimeZone(utc); - xep0091Date7Digit1MonthFormatter.setTimeZone(utc); - xep0091Date7Digit1MonthFormatter.setLenient(false); - xep0091Date7Digit2MonthFormatter.setTimeZone(utc); - xep0091Date7Digit2MonthFormatter.setLenient(false); - - couplings.add(new PatternCouplings(datePattern, dateFormatter)); - couplings.add(new PatternCouplings(dateTimePattern, dateTimeFormatter, true)); - couplings.add(new PatternCouplings(dateTimeNoMillisPattern, dateTimeNoMillisFormatter, true)); - couplings.add(new PatternCouplings(timePattern, timeFormatter, true)); - couplings.add(new PatternCouplings(timeNoZonePattern, timeNoZoneFormatter)); - couplings.add(new PatternCouplings(timeNoMillisPattern, timeNoMillisFormatter, true)); - couplings.add(new PatternCouplings(timeNoMillisNoZonePattern, timeNoMillisNoZoneFormatter)); - } private static final char[] QUOTE_ENCODE = """.toCharArray(); private static final char[] APOS_ENCODE = "'".toCharArray(); @@ -115,161 +36,6 @@ public class StringUtils { 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. - * - * @param dateString the date string to parse - * @return the parsed Date - * @throws ParseException if the specified string cannot be parsed - * @deprecated Use {@link #parseDate(String)} instead. - * - */ - public static Date parseXEP0082Date(String dateString) throws ParseException { - return parseDate(dateString); - } - - /** - * Parses the given date string in either of the three profiles of XEP-0082 - XMPP Date and Time Profiles - * or XEP-0091 - Legacy Delayed Delivery format. - *

- * This method uses internal date formatters and is thus threadsafe. - * @param dateString the date string to parse - * @return the parsed Date - * @throws ParseException if the specified string cannot be parsed - */ - public static Date parseDate(String dateString) throws ParseException { - Matcher matcher = xep0091Pattern.matcher(dateString); - - /* - * if date is in XEP-0091 format handle ambiguous dates missing the - * leading zero in month and day - */ - if (matcher.matches()) { - int length = dateString.split("T")[0].length(); - - if (length < 8) { - Date date = handleDateWithMissingLeadingZeros(dateString, length); - - if (date != null) - return date; - } - else { - synchronized (xep0091Formatter) { - return xep0091Formatter.parse(dateString); - } - } - } - else { - for (PatternCouplings coupling : couplings) { - matcher = coupling.pattern.matcher(dateString); - - if (matcher.matches()) - { - if (coupling.needToConvertTimeZone) { - dateString = coupling.convertTime(dateString); - } - - synchronized (coupling.formatter) { - return coupling.formatter.parse(dateString); - } - } - } - } - - /* - * We assume it is the XEP-0082 DateTime profile with no milliseconds at this point. If it isn't, is is just not parseable, then we attempt - * to parse it regardless and let it throw the ParseException. - */ - synchronized (dateTimeNoMillisFormatter) { - return dateTimeNoMillisFormatter.parse(dateString); - } - } - - /** - * 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 - * @param dateLength - * @param noFuture - * @return the parsed date - * @throws ParseException The date string was of an unknown format - */ - private static Date handleDateWithMissingLeadingZeros(String stampString, int dateLength) throws ParseException { - if (dateLength == 6) { - synchronized (xep0091Date6DigitFormatter) { - return xep0091Date6DigitFormatter.parse(stampString); - } - } - Calendar now = Calendar.getInstance(); - - Calendar oneDigitMonth = parseXEP91Date(stampString, xep0091Date7Digit1MonthFormatter); - Calendar twoDigitMonth = parseXEP91Date(stampString, xep0091Date7Digit2MonthFormatter); - - List dates = filterDatesBefore(now, oneDigitMonth, twoDigitMonth); - - if (!dates.isEmpty()) { - return determineNearestDate(now, dates).getTime(); - } - return null; - } - - private static Calendar parseXEP91Date(String stampString, DateFormat dateFormat) { - try { - synchronized (dateFormat) { - dateFormat.parse(stampString); - return dateFormat.getCalendar(); - } - } - catch (ParseException e) { - return null; - } - } - - private static 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 static 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); - } - - /** - * 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 (dateTimeFormatter) { - return dateTimeFormatter.format(date); - } - } - - public static String formatDate(Date toFormat, DateFormatType type) - { - return null; - } - /** * Returns the name portion of a XMPP address. For example, for the * address "matt@jivesoftware.com/Smack", "matt" would be returned. If no @@ -804,38 +570,4 @@ public class StringUtils { return new String(randBuffer); } - private StringUtils() { - // Not instantiable. - } - - private static class PatternCouplings { - Pattern pattern; - DateFormat formatter; - boolean needToConvertTimeZone = false; - - public PatternCouplings(Pattern datePattern, DateFormat dateFormat) { - pattern = datePattern; - formatter = dateFormat; - } - - public PatternCouplings(Pattern datePattern, DateFormat dateFormat, boolean shouldConvertToRFC822) { - pattern = datePattern; - formatter = dateFormat; - needToConvertTimeZone = shouldConvertToRFC822; - } - - public String convertTime(String dateString) { - if (dateString.charAt(dateString.length() - 1) == 'Z') { - return dateString.replace("Z", "+0000"); - } - else { - // If the time zone wasn't specified with 'Z', then it's in - // ISO8601 format (i.e. '(+|-)HH:mm') - // RFC822 needs a similar format just without the colon (i.e. - // '(+|-)HHmm)'), so remove it - return dateString.replaceAll("([\\+\\-]\\d\\d):(\\d\\d)","$1$2"); - } - } - } - } diff --git a/core/src/main/java/org/jivesoftware/smack/util/XmppDateTime.java b/core/src/main/java/org/jivesoftware/smack/util/XmppDateTime.java new file mode 100644 index 000000000..6b002d23e --- /dev/null +++ b/core/src/main/java/org/jivesoftware/smack/util/XmppDateTime.java @@ -0,0 +1,325 @@ +/** + * + * Copyright the original author or authors + * + * 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.smack.util; + +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.List; +import java.util.TimeZone; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class XmppDateTime { + + private static final DateFormatType dateFormatter = DateFormatType.XEP_0082_DATE_PROFILE; + private static final Pattern datePattern = Pattern.compile("^\\d+-\\d+-\\d+$"); + + private static final DateFormatType timeFormatter = DateFormatType.XEP_0082_TIME_MILLIS_ZONE_PROFILE; + private static final Pattern timePattern = Pattern.compile("^(\\d+:){2}\\d+.\\d+(Z|([+-](\\d+:\\d+)))$"); + private static final DateFormatType timeNoZoneFormatter = DateFormatType.XEP_0082_TIME_MILLIS_PROFILE; + private static final Pattern timeNoZonePattern = Pattern.compile("^(\\d+:){2}\\d+.\\d+$"); + + private static final DateFormatType timeNoMillisFormatter = DateFormatType.XEP_0082_TIME_ZONE_PROFILE; + private static final Pattern timeNoMillisPattern = Pattern.compile("^(\\d+:){2}\\d+(Z|([+-](\\d+:\\d+)))$"); + private static final DateFormatType timeNoMillisNoZoneFormatter = DateFormatType.XEP_0082_TIME_PROFILE; + private static final Pattern timeNoMillisNoZonePattern = Pattern.compile("^(\\d+:){2}\\d+$"); + + private static final DateFormatType dateTimeFormatter = DateFormatType.XEP_0082_DATETIME_MILLIS_PROFILE; + private static final Pattern dateTimePattern = Pattern.compile("^\\d+(-\\d+){2}+T(\\d+:){2}\\d+.\\d+(Z|([+-](\\d+:\\d+)))?$"); + private static final DateFormatType dateTimeNoMillisFormatter = DateFormatType.XEP_0082_DATETIME_PROFILE; + private static final Pattern dateTimeNoMillisPattern = Pattern.compile("^\\d+(-\\d+){2}+T(\\d+:){2}\\d+(Z|([+-](\\d+:\\d+)))?$"); + + private static final DateFormat xep0091Formatter = new SimpleDateFormat("yyyyMMdd'T'HH:mm:ss"); + private static final DateFormat xep0091Date6DigitFormatter = new SimpleDateFormat( + "yyyyMd'T'HH:mm:ss"); + private static final DateFormat xep0091Date7Digit1MonthFormatter = new SimpleDateFormat( + "yyyyMdd'T'HH:mm:ss"); + private static final DateFormat xep0091Date7Digit2MonthFormatter = new SimpleDateFormat( + "yyyyMMd'T'HH:mm:ss"); + private static final Pattern xep0091Pattern = Pattern.compile("^\\d+T\\d+:\\d+:\\d+$"); + + public static enum DateFormatType { + // @formatter:off + XEP_0082_DATE_PROFILE("yyyy-MM-dd"), + XEP_0082_DATETIME_PROFILE("yyyy-MM-dd'T'HH:mm:ssZ"), + XEP_0082_DATETIME_MILLIS_PROFILE("yyyy-MM-dd'T'HH:mm:ss.SSSZ"), + XEP_0082_TIME_PROFILE("hh:mm:ss"), + XEP_0082_TIME_ZONE_PROFILE("hh:mm:ssZ"), + XEP_0082_TIME_MILLIS_PROFILE("hh:mm:ss.SSS"), + XEP_0082_TIME_MILLIS_ZONE_PROFILE("hh:mm:ss.SSSZ"), + XEP_0091_DATETIME("yyyyMMdd'T'HH:mm:ss"); + // @formatter:on + + private final String FORMAT_STRING; + private final DateFormat FORMATTER; + private final boolean CONVERT_TIMEZONE; + + private DateFormatType(String dateFormat) { + FORMAT_STRING = dateFormat; + FORMATTER = new SimpleDateFormat(FORMAT_STRING); + FORMATTER.setTimeZone(TimeZone.getTimeZone("UTC")); + CONVERT_TIMEZONE = dateFormat.charAt(dateFormat.length() - 1) == 'Z'; + } + + public String format(Date date) { + String res; + synchronized(FORMATTER) { + res = FORMATTER.format(date); + } + if (CONVERT_TIMEZONE) { + res = convertRfc822TimezoneToXep82(res); + } + return res; + } + + public Date parse(String dateString) throws ParseException { + if (CONVERT_TIMEZONE) { + dateString = convertXep82TimezoneToRfc822(dateString); + } + synchronized(FORMATTER) { + return FORMATTER.parse(dateString); + } + } + } + + private static final List couplings = new ArrayList(); + + static { + TimeZone utc = TimeZone.getTimeZone("UTC"); + + xep0091Formatter.setTimeZone(utc); + xep0091Date6DigitFormatter.setTimeZone(utc); + xep0091Date7Digit1MonthFormatter.setTimeZone(utc); + xep0091Date7Digit1MonthFormatter.setLenient(false); + xep0091Date7Digit2MonthFormatter.setTimeZone(utc); + xep0091Date7Digit2MonthFormatter.setLenient(false); + + couplings.add(new PatternCouplings(datePattern, dateFormatter)); + couplings.add(new PatternCouplings(dateTimePattern, dateTimeFormatter)); + couplings.add(new PatternCouplings(dateTimeNoMillisPattern, dateTimeNoMillisFormatter)); + couplings.add(new PatternCouplings(timePattern, timeFormatter)); + couplings.add(new PatternCouplings(timeNoZonePattern, timeNoZoneFormatter)); + couplings.add(new PatternCouplings(timeNoMillisPattern, timeNoMillisFormatter)); + couplings.add(new PatternCouplings(timeNoMillisNoZonePattern, timeNoMillisNoZoneFormatter)); + } + + /** + * Parses the given date string in the XEP-0082 - XMPP Date and Time Profiles. + * + * @param dateString the date string to parse + * @return the parsed Date + * @throws ParseException if the specified string cannot be parsed + * @deprecated Use {@link #parseDate(String)} instead. + */ + public static Date parseXEP0082Date(String dateString) throws ParseException { + return parseDate(dateString); + } + + /** + * Parses the given date string in either of the three profiles of XEP-0082 - XMPP Date and Time Profiles or + * XEP-0091 - Legacy Delayed Delivery + * format. + *

+ * This method uses internal date formatters and is thus threadsafe. + * + * @param dateString the date string to parse + * @return the parsed Date + * @throws ParseException if the specified string cannot be parsed + */ + public static Date parseDate(String dateString) throws ParseException { + Matcher matcher = xep0091Pattern.matcher(dateString); + + /* + * if date is in XEP-0091 format handle ambiguous dates missing the leading zero in month + * and day + */ + if (matcher.matches()) { + int length = dateString.split("T")[0].length(); + + if (length < 8) { + Date date = handleDateWithMissingLeadingZeros(dateString, length); + + if (date != null) + return date; + } + else { + synchronized (xep0091Formatter) { + return xep0091Formatter.parse(dateString); + } + } + } + else { + for (PatternCouplings coupling : couplings) { + matcher = coupling.pattern.matcher(dateString); + + if (matcher.matches()) { + return coupling.formatter.parse(dateString); + } + } + } + + /* + * We assume it is the XEP-0082 DateTime profile with no milliseconds at this point. If it + * isn't, is is just not parseable, then we attempt to parse it regardless and let it throw + * the ParseException. + */ + synchronized (dateTimeNoMillisFormatter) { + return dateTimeNoMillisFormatter.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 (dateTimeFormatter) { + return dateTimeFormatter.format(date); + } + } + + /** + * Converts a XEP-0082 date String's time zone definition into a RFC822 time zone definition. + * The major difference is that XEP-0082 uses a smicolon between hours and minutes and RFC822 + * does not. + * + * @param dateString + * @return + */ + public static String convertXep82TimezoneToRfc822(String dateString) { + if (dateString.charAt(dateString.length() - 1) == 'Z') { + return dateString.replace("Z", "+0000"); + } + else { + // If the time zone wasn't specified with 'Z', then it's in + // ISO8601 format (i.e. '(+|-)HH:mm') + // RFC822 needs a similar format just without the colon (i.e. + // '(+|-)HHmm)'), so remove it + return dateString.replaceAll("([\\+\\-]\\d\\d):(\\d\\d)", "$1$2"); + } + } + + public static String convertRfc822TimezoneToXep82(String dateString) { + int length = dateString.length(); + String res = dateString.substring(0, length -2); + res += ':'; + res += dateString.substring(length - 2, length); + return res; + } + + /** + * Converts a time zone to the String format as specified in XEP-0082 + * + * @param timeZone + * @return + */ + public static String asString(TimeZone timeZone) { + int rawOffset = timeZone.getRawOffset(); + int hours = rawOffset / (1000*60*60); + int minutes = Math.abs((rawOffset / (1000*60)) - (hours * 60)); + return String.format("%+d:%02d", hours, minutes); + } + + /** + * 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 + * @param dateLength + * @param noFuture + * @return the parsed date + * @throws ParseException The date string was of an unknown format + */ + private static Date handleDateWithMissingLeadingZeros(String stampString, int dateLength) + throws ParseException { + if (dateLength == 6) { + synchronized (xep0091Date6DigitFormatter) { + return xep0091Date6DigitFormatter.parse(stampString); + } + } + Calendar now = Calendar.getInstance(); + + Calendar oneDigitMonth = parseXEP91Date(stampString, xep0091Date7Digit1MonthFormatter); + Calendar twoDigitMonth = parseXEP91Date(stampString, xep0091Date7Digit2MonthFormatter); + + List dates = filterDatesBefore(now, oneDigitMonth, twoDigitMonth); + + if (!dates.isEmpty()) { + return determineNearestDate(now, dates).getTime(); + } + return null; + } + + private static Calendar parseXEP91Date(String stampString, DateFormat dateFormat) { + try { + synchronized (dateFormat) { + dateFormat.parse(stampString); + return dateFormat.getCalendar(); + } + } + catch (ParseException e) { + return null; + } + } + + private static 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 static 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); + } + + private static class PatternCouplings { + final Pattern pattern; + final DateFormatType formatter; + + public PatternCouplings(Pattern datePattern, DateFormatType dateFormat) { + pattern = datePattern; + formatter = dateFormat; + } + } +} diff --git a/core/src/test/java/org/jivesoftware/smack/util/StringUtilsTest.java b/core/src/test/java/org/jivesoftware/smack/util/StringUtilsTest.java index 4ddcce388..2f62cb213 100644 --- a/core/src/test/java/org/jivesoftware/smack/util/StringUtilsTest.java +++ b/core/src/test/java/org/jivesoftware/smack/util/StringUtilsTest.java @@ -22,11 +22,6 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; -import java.text.ParseException; -import java.util.Calendar; -import java.util.Date; -import java.util.TimeZone; - import org.junit.Test; /** @@ -216,225 +211,4 @@ public class StringUtilsTest { assertEquals(error, result, StringUtils.parseServer("user@yahoo.myjabber.net/registred")); assertEquals(error, result, StringUtils.parseServer("user@yahoo.myjabber.net")); } - - @Test - public void parseXep0082Date() throws Exception - { - Date date = StringUtils.parseDate("1971-07-21"); - Calendar cal = Calendar.getInstance(); - cal.setTime(date); - cal.setTimeZone(TimeZone.getTimeZone("GMT")); - assertEquals(1971, cal.get(Calendar.YEAR)); - assertEquals(6, cal.get(Calendar.MONTH)); - assertEquals(21, cal.get(Calendar.DAY_OF_MONTH)); - } - - @Test - public void parseXep0082Time() throws Exception - { - Date date = StringUtils.parseDate("02:56:15"); - Calendar cal = Calendar.getInstance(); - cal.setTime(date); - cal.setTimeZone(TimeZone.getTimeZone("GMT")); - assertEquals(2, cal.get(Calendar.HOUR_OF_DAY)); - assertEquals(56, cal.get(Calendar.MINUTE)); - assertEquals(15, cal.get(Calendar.SECOND)); - } - - @Test - public void parseXep0082TimeUTC() throws Exception - { - Date date = StringUtils.parseDate("02:56:15Z"); - Calendar cal = Calendar.getInstance(); - cal.setTime(date); - cal.setTimeZone(TimeZone.getTimeZone("GMT")); - assertEquals(2, cal.get(Calendar.HOUR_OF_DAY)); - assertEquals(56, cal.get(Calendar.MINUTE)); - assertEquals(15, cal.get(Calendar.SECOND)); - } - - @Test - public void parseXep0082TimeWithZone() throws Exception - { - Date date = StringUtils.parseDate("04:40:15+02:30"); - Calendar cal = Calendar.getInstance(); - cal.setTime(date); - cal.setTimeZone(TimeZone.getTimeZone("GMT")); - assertEquals(2, cal.get(Calendar.HOUR_OF_DAY)); - assertEquals(10, cal.get(Calendar.MINUTE)); - assertEquals(15, cal.get(Calendar.SECOND)); - } - - @Test - public void parseXep0082TimeWithMillis() throws Exception - { - Date date = StringUtils.parseDate("02:56:15.123"); - Calendar cal = Calendar.getInstance(); - cal.setTime(date); - cal.setTimeZone(TimeZone.getTimeZone("GMT")); - assertEquals(2, cal.get(Calendar.HOUR_OF_DAY)); - assertEquals(56, cal.get(Calendar.MINUTE)); - assertEquals(15, cal.get(Calendar.SECOND)); - assertEquals(123, cal.get(Calendar.MILLISECOND)); - } - - @Test - public void parseXep0082TimeWithMillisUTC() throws Exception - { - Date date = StringUtils.parseDate("02:56:15.123Z"); - Calendar cal = Calendar.getInstance(); - cal.setTime(date); - cal.setTimeZone(TimeZone.getTimeZone("GMT")); - assertEquals(2, cal.get(Calendar.HOUR_OF_DAY)); - assertEquals(56, cal.get(Calendar.MINUTE)); - assertEquals(15, cal.get(Calendar.SECOND)); - assertEquals(123, cal.get(Calendar.MILLISECOND)); - } - - @Test - public void parseXep0082TimeWithMillisZone() throws Exception - { - Date date = StringUtils.parseDate("02:56:15.123+01:00"); - Calendar cal = Calendar.getInstance(); - cal.setTime(date); - cal.setTimeZone(TimeZone.getTimeZone("GMT")); - assertEquals(1, cal.get(Calendar.HOUR_OF_DAY)); - assertEquals(56, cal.get(Calendar.MINUTE)); - assertEquals(15, cal.get(Calendar.SECOND)); - assertEquals(123, cal.get(Calendar.MILLISECOND)); - } - - @Test - public void parseXep0082DateTimeUTC() throws Exception - { - Date date = StringUtils.parseDate("1971-07-21T02:56:15Z"); - Calendar cal = Calendar.getInstance(); - cal.setTime(date); - cal.setTimeZone(TimeZone.getTimeZone("GMT")); - assertEquals(1971, cal.get(Calendar.YEAR)); - assertEquals(6, cal.get(Calendar.MONTH)); - assertEquals(21, cal.get(Calendar.DAY_OF_MONTH)); - assertEquals(2, cal.get(Calendar.HOUR_OF_DAY)); - assertEquals(56, cal.get(Calendar.MINUTE)); - assertEquals(15, cal.get(Calendar.SECOND)); - } - - @Test - public void parseXep0082DateTimeZone() throws Exception - { - Date date = StringUtils.parseDate("1971-07-21T02:56:15-01:00"); - Calendar cal = Calendar.getInstance(); - cal.setTime(date); - cal.setTimeZone(TimeZone.getTimeZone("GMT")); - assertEquals(1971, cal.get(Calendar.YEAR)); - assertEquals(6, cal.get(Calendar.MONTH)); - assertEquals(21, cal.get(Calendar.DAY_OF_MONTH)); - assertEquals(3, cal.get(Calendar.HOUR_OF_DAY)); - assertEquals(56, cal.get(Calendar.MINUTE)); - assertEquals(15, cal.get(Calendar.SECOND)); - } - - @Test - public void parseXep0082DateTimeWithMillisUTC() throws Exception - { - Date date = StringUtils.parseDate("1971-07-21T02:56:15.123Z"); - Calendar cal = Calendar.getInstance(); - cal.setTime(date); - cal.setTimeZone(TimeZone.getTimeZone("GMT")); - assertEquals(1971, cal.get(Calendar.YEAR)); - assertEquals(6, cal.get(Calendar.MONTH)); - assertEquals(21, cal.get(Calendar.DAY_OF_MONTH)); - assertEquals(2, cal.get(Calendar.HOUR_OF_DAY)); - assertEquals(56, cal.get(Calendar.MINUTE)); - assertEquals(15, cal.get(Calendar.SECOND)); - assertEquals(123, cal.get(Calendar.MILLISECOND)); - } - - @Test - public void parseXep0082DateTimeWithMillisZone() throws Exception - { - Date date = StringUtils.parseDate("1971-07-21T02:56:15.123-01:00"); - Calendar cal = Calendar.getInstance(); - cal.setTime(date); - cal.setTimeZone(TimeZone.getTimeZone("GMT")); - assertEquals(1971, cal.get(Calendar.YEAR)); - assertEquals(6, cal.get(Calendar.MONTH)); - assertEquals(21, cal.get(Calendar.DAY_OF_MONTH)); - assertEquals(3, cal.get(Calendar.HOUR_OF_DAY)); - assertEquals(56, cal.get(Calendar.MINUTE)); - assertEquals(15, cal.get(Calendar.SECOND)); - assertEquals(123, cal.get(Calendar.MILLISECOND)); - } - - @Test - public void parseXep0091() throws Exception - { - Date date = StringUtils.parseDate("20020910T23:08:25"); - Calendar cal = Calendar.getInstance(); - cal.setTime(date); - cal.setTimeZone(TimeZone.getTimeZone("GMT")); - assertEquals(2002, cal.get(Calendar.YEAR)); - assertEquals(8, cal.get(Calendar.MONTH)); - assertEquals(10, cal.get(Calendar.DAY_OF_MONTH)); - assertEquals(23, cal.get(Calendar.HOUR_OF_DAY)); - assertEquals(8, cal.get(Calendar.MINUTE)); - assertEquals(25, cal.get(Calendar.SECOND)); - } - - @Test - public void parseXep0091NoLeading0() throws Exception - { - Date date = StringUtils.parseDate("200291T23:08:25"); - Calendar cal = Calendar.getInstance(); - cal.setTime(date); - cal.setTimeZone(TimeZone.getTimeZone("GMT")); - assertEquals(2002, cal.get(Calendar.YEAR)); - assertEquals(8, cal.get(Calendar.MONTH)); - assertEquals(1, cal.get(Calendar.DAY_OF_MONTH)); - assertEquals(23, cal.get(Calendar.HOUR_OF_DAY)); - assertEquals(8, cal.get(Calendar.MINUTE)); - assertEquals(25, cal.get(Calendar.SECOND)); - } - - @Test - public void parseXep0091AmbiguousMonthDay() throws Exception - { - Date date = StringUtils.parseDate("2002101T23:08:25"); - Calendar cal = Calendar.getInstance(); - cal.setTime(date); - cal.setTimeZone(TimeZone.getTimeZone("GMT")); - assertEquals(2002, cal.get(Calendar.YEAR)); - assertEquals(9, cal.get(Calendar.MONTH)); - assertEquals(1, cal.get(Calendar.DAY_OF_MONTH)); - assertEquals(23, cal.get(Calendar.HOUR_OF_DAY)); - assertEquals(8, cal.get(Calendar.MINUTE)); - assertEquals(25, cal.get(Calendar.SECOND)); - } - - @Test - public void parseXep0091SingleDigitMonth() throws Exception - { - Date date = StringUtils.parseDate("2002130T23:08:25"); - Calendar cal = Calendar.getInstance(); - cal.setTime(date); - cal.setTimeZone(TimeZone.getTimeZone("GMT")); - assertEquals(2002, cal.get(Calendar.YEAR)); - assertEquals(0, cal.get(Calendar.MONTH)); - assertEquals(30, cal.get(Calendar.DAY_OF_MONTH)); - assertEquals(23, cal.get(Calendar.HOUR_OF_DAY)); - assertEquals(8, cal.get(Calendar.MINUTE)); - assertEquals(25, cal.get(Calendar.SECOND)); - } - - @Test (expected=ParseException.class) - public void parseNoMonthDay() throws Exception - { - StringUtils.parseDate("2002T23:08:25"); - } - - @Test (expected=ParseException.class) - public void parseNoYear() throws Exception - { - StringUtils.parseDate("130T23:08:25"); - } } diff --git a/core/src/test/java/org/jivesoftware/smack/util/XmppDateTimeTest.java b/core/src/test/java/org/jivesoftware/smack/util/XmppDateTimeTest.java new file mode 100644 index 000000000..48b59cfa1 --- /dev/null +++ b/core/src/test/java/org/jivesoftware/smack/util/XmppDateTimeTest.java @@ -0,0 +1,232 @@ +/** + * + * Copyright 2003-2007 Jive Software. + * + * 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.smack.util; + +import static org.junit.Assert.assertEquals; + +import java.text.ParseException; +import java.util.Calendar; +import java.util.Date; +import java.util.TimeZone; + +import org.junit.Test; + +public class XmppDateTimeTest { + @Test + public void parseXep0082Date() throws Exception { + Date date = XmppDateTime.parseDate("1971-07-21"); + Calendar cal = Calendar.getInstance(); + cal.setTime(date); + cal.setTimeZone(TimeZone.getTimeZone("GMT")); + assertEquals(1971, cal.get(Calendar.YEAR)); + assertEquals(6, cal.get(Calendar.MONTH)); + assertEquals(21, cal.get(Calendar.DAY_OF_MONTH)); + } + + @Test + public void parseXep0082Time() throws Exception { + Date date = XmppDateTime.parseDate("02:56:15"); + Calendar cal = Calendar.getInstance(); + cal.setTime(date); + cal.setTimeZone(TimeZone.getTimeZone("GMT")); + assertEquals(2, cal.get(Calendar.HOUR_OF_DAY)); + assertEquals(56, cal.get(Calendar.MINUTE)); + assertEquals(15, cal.get(Calendar.SECOND)); + } + + @Test + public void parseXep0082TimeUTC() throws Exception { + Date date = XmppDateTime.parseDate("02:56:15Z"); + Calendar cal = Calendar.getInstance(); + cal.setTime(date); + cal.setTimeZone(TimeZone.getTimeZone("GMT")); + assertEquals(2, cal.get(Calendar.HOUR_OF_DAY)); + assertEquals(56, cal.get(Calendar.MINUTE)); + assertEquals(15, cal.get(Calendar.SECOND)); + } + + @Test + public void parseXep0082TimeWithZone() throws Exception { + Date date = XmppDateTime.parseDate("04:40:15+02:30"); + Calendar cal = Calendar.getInstance(); + cal.setTime(date); + cal.setTimeZone(TimeZone.getTimeZone("GMT")); + assertEquals(2, cal.get(Calendar.HOUR_OF_DAY)); + assertEquals(10, cal.get(Calendar.MINUTE)); + assertEquals(15, cal.get(Calendar.SECOND)); + } + + @Test + public void parseXep0082TimeWithMillis() throws Exception { + Date date = XmppDateTime.parseDate("02:56:15.123"); + Calendar cal = Calendar.getInstance(); + cal.setTime(date); + cal.setTimeZone(TimeZone.getTimeZone("GMT")); + assertEquals(2, cal.get(Calendar.HOUR_OF_DAY)); + assertEquals(56, cal.get(Calendar.MINUTE)); + assertEquals(15, cal.get(Calendar.SECOND)); + assertEquals(123, cal.get(Calendar.MILLISECOND)); + } + + @Test + public void parseXep0082TimeWithMillisUTC() throws Exception { + Date date = XmppDateTime.parseDate("02:56:15.123Z"); + Calendar cal = Calendar.getInstance(); + cal.setTime(date); + cal.setTimeZone(TimeZone.getTimeZone("GMT")); + assertEquals(2, cal.get(Calendar.HOUR_OF_DAY)); + assertEquals(56, cal.get(Calendar.MINUTE)); + assertEquals(15, cal.get(Calendar.SECOND)); + assertEquals(123, cal.get(Calendar.MILLISECOND)); + } + + @Test + public void parseXep0082TimeWithMillisZone() throws Exception { + Date date = XmppDateTime.parseDate("02:56:15.123+01:00"); + Calendar cal = Calendar.getInstance(); + cal.setTime(date); + cal.setTimeZone(TimeZone.getTimeZone("GMT")); + assertEquals(1, cal.get(Calendar.HOUR_OF_DAY)); + assertEquals(56, cal.get(Calendar.MINUTE)); + assertEquals(15, cal.get(Calendar.SECOND)); + assertEquals(123, cal.get(Calendar.MILLISECOND)); + } + + @Test + public void parseXep0082DateTimeUTC() throws Exception { + Date date = XmppDateTime.parseDate("1971-07-21T02:56:15Z"); + Calendar cal = Calendar.getInstance(); + cal.setTime(date); + cal.setTimeZone(TimeZone.getTimeZone("GMT")); + assertEquals(1971, cal.get(Calendar.YEAR)); + assertEquals(6, cal.get(Calendar.MONTH)); + assertEquals(21, cal.get(Calendar.DAY_OF_MONTH)); + assertEquals(2, cal.get(Calendar.HOUR_OF_DAY)); + assertEquals(56, cal.get(Calendar.MINUTE)); + assertEquals(15, cal.get(Calendar.SECOND)); + } + + @Test + public void parseXep0082DateTimeZone() throws Exception { + Date date = XmppDateTime.parseDate("1971-07-21T02:56:15-01:00"); + Calendar cal = Calendar.getInstance(); + cal.setTime(date); + cal.setTimeZone(TimeZone.getTimeZone("GMT")); + assertEquals(1971, cal.get(Calendar.YEAR)); + assertEquals(6, cal.get(Calendar.MONTH)); + assertEquals(21, cal.get(Calendar.DAY_OF_MONTH)); + assertEquals(3, cal.get(Calendar.HOUR_OF_DAY)); + assertEquals(56, cal.get(Calendar.MINUTE)); + assertEquals(15, cal.get(Calendar.SECOND)); + } + + @Test + public void parseXep0082DateTimeWithMillisUTC() throws Exception { + Date date = XmppDateTime.parseDate("1971-07-21T02:56:15.123Z"); + Calendar cal = Calendar.getInstance(); + cal.setTime(date); + cal.setTimeZone(TimeZone.getTimeZone("GMT")); + assertEquals(1971, cal.get(Calendar.YEAR)); + assertEquals(6, cal.get(Calendar.MONTH)); + assertEquals(21, cal.get(Calendar.DAY_OF_MONTH)); + assertEquals(2, cal.get(Calendar.HOUR_OF_DAY)); + assertEquals(56, cal.get(Calendar.MINUTE)); + assertEquals(15, cal.get(Calendar.SECOND)); + assertEquals(123, cal.get(Calendar.MILLISECOND)); + } + + @Test + public void parseXep0082DateTimeWithMillisZone() throws Exception { + Date date = XmppDateTime.parseDate("1971-07-21T02:56:15.123-01:00"); + Calendar cal = Calendar.getInstance(); + cal.setTime(date); + cal.setTimeZone(TimeZone.getTimeZone("GMT")); + assertEquals(1971, cal.get(Calendar.YEAR)); + assertEquals(6, cal.get(Calendar.MONTH)); + assertEquals(21, cal.get(Calendar.DAY_OF_MONTH)); + assertEquals(3, cal.get(Calendar.HOUR_OF_DAY)); + assertEquals(56, cal.get(Calendar.MINUTE)); + assertEquals(15, cal.get(Calendar.SECOND)); + assertEquals(123, cal.get(Calendar.MILLISECOND)); + } + + @Test + public void parseXep0091() throws Exception { + Date date = XmppDateTime.parseDate("20020910T23:08:25"); + Calendar cal = Calendar.getInstance(); + cal.setTime(date); + cal.setTimeZone(TimeZone.getTimeZone("GMT")); + assertEquals(2002, cal.get(Calendar.YEAR)); + assertEquals(8, cal.get(Calendar.MONTH)); + assertEquals(10, cal.get(Calendar.DAY_OF_MONTH)); + assertEquals(23, cal.get(Calendar.HOUR_OF_DAY)); + assertEquals(8, cal.get(Calendar.MINUTE)); + assertEquals(25, cal.get(Calendar.SECOND)); + } + + @Test + public void parseXep0091NoLeading0() throws Exception { + Date date = XmppDateTime.parseDate("200291T23:08:25"); + Calendar cal = Calendar.getInstance(); + cal.setTime(date); + cal.setTimeZone(TimeZone.getTimeZone("GMT")); + assertEquals(2002, cal.get(Calendar.YEAR)); + assertEquals(8, cal.get(Calendar.MONTH)); + assertEquals(1, cal.get(Calendar.DAY_OF_MONTH)); + assertEquals(23, cal.get(Calendar.HOUR_OF_DAY)); + assertEquals(8, cal.get(Calendar.MINUTE)); + assertEquals(25, cal.get(Calendar.SECOND)); + } + + @Test + public void parseXep0091AmbiguousMonthDay() throws Exception { + Date date = XmppDateTime.parseDate("2002101T23:08:25"); + Calendar cal = Calendar.getInstance(); + cal.setTime(date); + cal.setTimeZone(TimeZone.getTimeZone("GMT")); + assertEquals(2002, cal.get(Calendar.YEAR)); + assertEquals(9, cal.get(Calendar.MONTH)); + assertEquals(1, cal.get(Calendar.DAY_OF_MONTH)); + assertEquals(23, cal.get(Calendar.HOUR_OF_DAY)); + assertEquals(8, cal.get(Calendar.MINUTE)); + assertEquals(25, cal.get(Calendar.SECOND)); + } + + @Test + public void parseXep0091SingleDigitMonth() throws Exception { + Date date = XmppDateTime.parseDate("2002130T23:08:25"); + Calendar cal = Calendar.getInstance(); + cal.setTime(date); + cal.setTimeZone(TimeZone.getTimeZone("GMT")); + assertEquals(2002, cal.get(Calendar.YEAR)); + assertEquals(0, cal.get(Calendar.MONTH)); + assertEquals(30, cal.get(Calendar.DAY_OF_MONTH)); + assertEquals(23, cal.get(Calendar.HOUR_OF_DAY)); + assertEquals(8, cal.get(Calendar.MINUTE)); + assertEquals(25, cal.get(Calendar.SECOND)); + } + + @Test(expected = ParseException.class) + public void parseNoMonthDay() throws Exception { + XmppDateTime.parseDate("2002T23:08:25"); + } + + @Test(expected = ParseException.class) + public void parseNoYear() throws Exception { + XmppDateTime.parseDate("130T23:08:25"); + } +} diff --git a/extensions/src/main/java/org/jivesoftware/smackx/delay/packet/DelayInfo.java b/extensions/src/main/java/org/jivesoftware/smackx/delay/packet/DelayInfo.java index 5626ac2b6..0de4b52b6 100644 --- a/extensions/src/main/java/org/jivesoftware/smackx/delay/packet/DelayInfo.java +++ b/extensions/src/main/java/org/jivesoftware/smackx/delay/packet/DelayInfo.java @@ -18,7 +18,7 @@ package org.jivesoftware.smackx.delay.packet; import java.util.Date; -import org.jivesoftware.smack.util.StringUtils; +import org.jivesoftware.smack.util.XmppDateTime; /** * A decorator for the {@link DelayInformation} class to transparently support @@ -92,7 +92,7 @@ public class DelayInfo extends DelayInformation buf.append("<").append(getElementName()).append(" xmlns=\"").append(getNamespace()).append( "\""); buf.append(" stamp=\""); - buf.append(StringUtils.formatXEP0082Date(getStamp())); + buf.append(XmppDateTime.formatXEP0082Date(getStamp())); buf.append("\""); if (getFrom() != null && getFrom().length() > 0) { buf.append(" from=\"").append(getFrom()).append("\""); diff --git a/extensions/src/main/java/org/jivesoftware/smackx/delay/provider/DelayInformationProvider.java b/extensions/src/main/java/org/jivesoftware/smackx/delay/provider/DelayInformationProvider.java index 91f1ff380..b4ec2ccc7 100644 --- a/extensions/src/main/java/org/jivesoftware/smackx/delay/provider/DelayInformationProvider.java +++ b/extensions/src/main/java/org/jivesoftware/smackx/delay/provider/DelayInformationProvider.java @@ -21,7 +21,7 @@ import java.util.Date; import org.jivesoftware.smack.packet.PacketExtension; import org.jivesoftware.smack.provider.PacketExtensionProvider; -import org.jivesoftware.smack.util.StringUtils; +import org.jivesoftware.smack.util.XmppDateTime; import org.jivesoftware.smackx.delay.packet.DelayInformation; import org.xmlpull.v1.XmlPullParser; @@ -38,7 +38,7 @@ public class DelayInformationProvider implements PacketExtensionProvider { Date stamp = null; try { - stamp = StringUtils.parseDate(stampString); + stamp = XmppDateTime.parseDate(stampString); } catch (ParseException parseExc) { /* diff --git a/extensions/src/main/java/org/jivesoftware/smackx/disco/ServiceDiscoveryManager.java b/extensions/src/main/java/org/jivesoftware/smackx/disco/ServiceDiscoveryManager.java index 14532fef6..9022ef08b 100644 --- a/extensions/src/main/java/org/jivesoftware/smackx/disco/ServiceDiscoveryManager.java +++ b/extensions/src/main/java/org/jivesoftware/smackx/disco/ServiceDiscoveryManager.java @@ -645,6 +645,19 @@ public class ServiceDiscoveryManager { connection.createPacketCollectorAndSend(discoverItems).nextResultOrThrow(); } + /** + * Queries the remote jid for it's features and returns true if the given feature is found. + * + * @param jid + * @param feature + * @return + * @throws XMPPException + */ + public boolean supportsFeature(String jid, String feature) throws XMPPException { + DiscoverInfo result = discoverInfo(jid); + return result.containsFeature(feature); + } + /** * Entity Capabilities */ diff --git a/extensions/src/main/java/org/jivesoftware/smackx/pubsub/SubscribeForm.java b/extensions/src/main/java/org/jivesoftware/smackx/pubsub/SubscribeForm.java index 9c8e5dae7..d031d61e5 100644 --- a/extensions/src/main/java/org/jivesoftware/smackx/pubsub/SubscribeForm.java +++ b/extensions/src/main/java/org/jivesoftware/smackx/pubsub/SubscribeForm.java @@ -23,7 +23,7 @@ import java.util.Date; import java.util.Iterator; import java.util.UnknownFormatConversionException; -import org.jivesoftware.smack.util.StringUtils; +import org.jivesoftware.smack.util.XmppDateTime; import org.jivesoftware.smackx.xdata.Form; import org.jivesoftware.smackx.xdata.FormField; import org.jivesoftware.smackx.xdata.packet.DataForm; @@ -128,7 +128,7 @@ public class SubscribeForm extends Form String dateTime = getFieldValue(SubscribeOptionFields.expire); try { - return StringUtils.parseDate(dateTime); + return XmppDateTime.parseDate(dateTime); } catch (ParseException e) { @@ -146,7 +146,7 @@ public class SubscribeForm extends Form public void setExpiry(Date expire) { addField(SubscribeOptionFields.expire, FormField.TYPE_TEXT_SINGLE); - setAnswer(SubscribeOptionFields.expire.getFieldName(), StringUtils.formatXEP0082Date(expire)); + setAnswer(SubscribeOptionFields.expire.getFieldName(), XmppDateTime.formatXEP0082Date(expire)); } /** diff --git a/extensions/src/main/java/org/jivesoftware/smackx/si/packet/StreamInitiation.java b/extensions/src/main/java/org/jivesoftware/smackx/si/packet/StreamInitiation.java index da5afc54e..b43978297 100644 --- a/extensions/src/main/java/org/jivesoftware/smackx/si/packet/StreamInitiation.java +++ b/extensions/src/main/java/org/jivesoftware/smackx/si/packet/StreamInitiation.java @@ -21,6 +21,7 @@ import java.util.Date; import org.jivesoftware.smack.packet.IQ; import org.jivesoftware.smack.packet.PacketExtension; import org.jivesoftware.smack.util.StringUtils; +import org.jivesoftware.smack.util.XmppDateTime; import org.jivesoftware.smackx.xdata.packet.DataForm; /** @@ -345,7 +346,7 @@ public class StreamInitiation extends IQ { } if (getDate() != null) { - buffer.append("date=\"").append(StringUtils.formatXEP0082Date(date)).append("\" "); + buffer.append("date=\"").append(XmppDateTime.formatXEP0082Date(date)).append("\" "); } if (getHash() != null) { diff --git a/extensions/src/main/java/org/jivesoftware/smackx/si/provider/StreamInitiationProvider.java b/extensions/src/main/java/org/jivesoftware/smackx/si/provider/StreamInitiationProvider.java index c0f0512c9..c90cc88a7 100644 --- a/extensions/src/main/java/org/jivesoftware/smackx/si/provider/StreamInitiationProvider.java +++ b/extensions/src/main/java/org/jivesoftware/smackx/si/provider/StreamInitiationProvider.java @@ -23,7 +23,7 @@ import java.util.logging.Logger; import org.jivesoftware.smack.packet.IQ; import org.jivesoftware.smack.provider.IQProvider; -import org.jivesoftware.smack.util.StringUtils; +import org.jivesoftware.smack.util.XmppDateTime; import org.jivesoftware.smackx.si.packet.StreamInitiation; import org.jivesoftware.smackx.si.packet.StreamInitiation.File; import org.jivesoftware.smackx.xdata.packet.DataForm; @@ -98,7 +98,7 @@ public class StreamInitiationProvider implements IQProvider { Date fileDate = new Date(); if (date != null) { try { - fileDate = StringUtils.parseDate(date); + fileDate = XmppDateTime.parseDate(date); } catch (ParseException e) { // couldn't parse date, use current date-time } diff --git a/extensions/src/main/java/org/jivesoftware/smackx/time/EntityTimeManager.java b/extensions/src/main/java/org/jivesoftware/smackx/time/EntityTimeManager.java new file mode 100644 index 000000000..68871564c --- /dev/null +++ b/extensions/src/main/java/org/jivesoftware/smackx/time/EntityTimeManager.java @@ -0,0 +1,111 @@ +/** + * + * Copyright 2014 Florian Schmaus + * + * 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.time; + +import java.util.Map; +import java.util.WeakHashMap; + +import org.jivesoftware.smack.Connection; +import org.jivesoftware.smack.ConnectionCreationListener; +import org.jivesoftware.smack.Manager; +import org.jivesoftware.smack.PacketListener; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.filter.AndFilter; +import org.jivesoftware.smack.filter.IQTypeFilter; +import org.jivesoftware.smack.filter.PacketFilter; +import org.jivesoftware.smack.filter.PacketTypeFilter; +import org.jivesoftware.smack.packet.IQ.Type; +import org.jivesoftware.smack.packet.Packet; +import org.jivesoftware.smackx.disco.ServiceDiscoveryManager; +import org.jivesoftware.smackx.time.packet.Time; + +public class EntityTimeManager extends Manager { + + private static final Map INSTANCES = new WeakHashMap(); + + private static final PacketFilter TIME_PACKET_FILTER = new AndFilter(new PacketTypeFilter( + Time.class), new IQTypeFilter(Type.GET)); + + private static boolean autoEnable = true; + + static { + Connection.addConnectionCreationListener(new ConnectionCreationListener() { + public void connectionCreated(Connection connection) { + getInstanceFor(connection); + } + }); + } + + public static void setAutoEnable(boolean autoEnable) { + EntityTimeManager.autoEnable = autoEnable; + } + + public synchronized static EntityTimeManager getInstanceFor(Connection connection) { + EntityTimeManager entityTimeManager = INSTANCES.get(connection); + if (entityTimeManager == null) { + entityTimeManager = new EntityTimeManager(connection); + } + return entityTimeManager; + } + + private boolean enabled = false; + + private EntityTimeManager(Connection connection) { + super(connection); + INSTANCES.put(connection, this); + if (autoEnable) + enable(); + + connection.addPacketListener(new PacketListener() { + @Override + public void processPacket(Packet packet) { + if (!enabled) + return; + connection().sendPacket(Time.createResponse(packet)); + } + }, TIME_PACKET_FILTER); + } + + public synchronized void enable() { + if (enabled) + return; + ServiceDiscoveryManager sdm = ServiceDiscoveryManager.getInstanceFor(connection()); + sdm.addFeature(Time.NAMESPACE); + enabled = true; + } + + public synchronized void disable() { + if (!enabled) + return; + ServiceDiscoveryManager sdm = ServiceDiscoveryManager.getInstanceFor(connection()); + sdm.removeFeature(Time.NAMESPACE); + enabled = false; + } + + public boolean isTimeSupported(String jid) throws XMPPException { + return ServiceDiscoveryManager.getInstanceFor(connection()).supportsFeature(jid, Time.NAMESPACE); + } + + public Time getTime(String jid) throws XMPPException { + if (!isTimeSupported(jid)) + return null; + + Time request = new Time(); + Time response = (Time) connection().createPacketCollectorAndSend(request).nextResultOrThrow(); + return response; + } +} diff --git a/extensions/src/main/java/org/jivesoftware/smackx/time/packet/Time.java b/extensions/src/main/java/org/jivesoftware/smackx/time/packet/Time.java index ebe7cdcf2..2c2724cb8 100644 --- a/extensions/src/main/java/org/jivesoftware/smackx/time/packet/Time.java +++ b/extensions/src/main/java/org/jivesoftware/smackx/time/packet/Time.java @@ -1,6 +1,6 @@ /** * - * Copyright 2003-2007 Jive Software. + * Copyright 2003-2007 Jive Software, 2014 Florian Schmaus * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,16 +14,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package org.jivesoftware.smackx.time.packet; import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.packet.Packet; +import org.jivesoftware.smack.util.XmppDateTime; -import java.text.DateFormat; -import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; -import java.util.TimeZone; import java.util.logging.Level; import java.util.logging.Logger; @@ -31,49 +29,21 @@ import java.util.logging.Logger; * A Time IQ packet, which is used by XMPP clients to exchange their respective local * times. Clients that wish to fully support the entitity time protocol should register * a PacketListener for incoming time requests that then respond with the local time. - * This class can be used to request the time from other clients, such as in the - * following code snippet: * - *

- * // Request the time from a remote user.
- * Time timeRequest = new Time();
- * timeRequest.setType(IQ.Type.GET);
- * timeRequest.setTo(someUser@example.com/resource);
- *
- * // Create a packet collector to listen for a response.
- * PacketCollector collector = con.createPacketCollector(
- *                new PacketIDFilter(timeRequest.getPacketID()));
- *
- * con.sendPacket(timeRequest);
- *
- * // Wait up to 5 seconds for a result.
- * IQ result = (IQ)collector.nextResult(5000);
- * if (result != null && result.getType() == IQ.Type.RESULT) {
- *     Time timeResult = (Time)result;
- *     // Do something with result...
- * }

- * - * Warning: this is an non-standard protocol documented by - * XEP-0090. Because this is a - * non-standard protocol, it is subject to change. - * - * @author Matt Tucker + * @see http://www.xmpp.org/extensions/xep-0202.html + * @author Florian Schmaus */ public class Time extends IQ { + public static final String NAMESPACE = "urn:xmpp:time"; + public static final String ELEMENT = "time"; + private static final Logger LOGGER = Logger.getLogger(Time.class.getName()); - - private static SimpleDateFormat utcFormat = new SimpleDateFormat("yyyyMMdd'T'HH:mm:ss"); - private static DateFormat displayFormat = DateFormat.getDateTimeInstance(); - private String utc = null; - private String tz = null; - private String display = null; + private String utc; + private String tzo; - /** - * Creates a new Time instance with empty values for all fields. - */ public Time() { - + setType(Type.GET); } /** @@ -83,12 +53,9 @@ public class Time extends IQ { * @param cal the time value. */ public Time(Calendar cal) { - TimeZone timeZone = cal.getTimeZone(); - tz = cal.getTimeZone().getID(); - display = displayFormat.format(cal.getTime()); + tzo = XmppDateTime.asString(cal.getTimeZone()); // Convert local time to the UTC time. - utc = utcFormat.format(new Date( - cal.getTimeInMillis() - timeZone.getOffset(cal.getTimeInMillis()))); + utc = XmppDateTime.formatXEP0082Date(cal.getTime()); } /** @@ -102,11 +69,7 @@ public class Time extends IQ { } Date date = null; try { - Calendar cal = Calendar.getInstance(); - // Convert the UTC time to local time. - cal.setTime(new Date(utcFormat.parse(utc).getTime() + - cal.getTimeZone().getOffset(cal.getTimeInMillis()))); - date = cal.getTime(); + date = XmppDateTime.parseDate(utc); } catch (Exception e) { LOGGER.log(Level.SEVERE, "Error getting local time", e); @@ -120,13 +83,10 @@ public class Time extends IQ { * @param time the current local time. */ public void setTime(Date time) { - // Convert local time to UTC time. - utc = utcFormat.format(new Date( - time.getTime() - TimeZone.getDefault().getOffset(time.getTime()))); } /** - * Returns the time as a UTC formatted String using the format CCYYMMDDThh:mm:ss. + * Returns the time as a UTC formatted String using the format CCYY-MM-DDThh:mm:ssZ. * * @return the time as a UTC formatted String. */ @@ -135,13 +95,12 @@ public class Time extends IQ { } /** - * Sets the time using UTC formatted String in the format CCYYMMDDThh:mm:ss. + * Sets the time using UTC formatted String in the format CCYY-MM-DDThh:mm:ssZ. * * @param utc the time using a formatted String. */ public void setUtc(String utc) { this.utc = utc; - } /** @@ -149,50 +108,34 @@ public class Time extends IQ { * * @return the time zone. */ - public String getTz() { - return tz; + public String getTzo() { + return tzo; } /** - * Sets the time zone. + * Sets the time zone offset. * - * @param tz the time zone. + * @param tzo the time zone offset. */ - public void setTz(String tz) { - this.tz = tz; + public void setTzo(String tzo) { + this.tzo = tzo; } - /** - * Returns the local (non-utc) time in human-friendly format. - * - * @return the local time in human-friendly format. - */ - public String getDisplay() { - return display; - } - - /** - * Sets the local time in human-friendly format. - * - * @param display the local time in human-friendly format. - */ - public void setDisplay(String display) { - this.display = display; + public static Time createResponse(Packet request) { + Time time = new Time(Calendar.getInstance()); + time.setType(Type.RESULT); + time.setTo(request.getFrom()); + return time; } public String getChildElementXML() { StringBuilder buf = new StringBuilder(); - buf.append(""); + buf.append("<" + ELEMENT + " xmlns='" + NAMESPACE + "'>"); if (utc != null) { buf.append("").append(utc).append(""); + buf.append("").append(tzo).append(""); } - if (tz != null) { - buf.append("").append(tz).append(""); - } - if (display != null) { - buf.append("").append(display).append(""); - } - buf.append(""); + buf.append(""); return buf.toString(); } } diff --git a/extensions/src/main/resources/org.jivesoftware.smackx/extensions.providers b/extensions/src/main/resources/org.jivesoftware.smackx/extensions.providers index 27e98c190..d2298797a 100644 --- a/extensions/src/main/resources/org.jivesoftware.smackx/extensions.providers +++ b/extensions/src/main/resources/org.jivesoftware.smackx/extensions.providers @@ -1,19 +1,19 @@ - + - query - jabber:iq:private - org.jivesoftware.smackx.iqprivate.PrivateDataManager$PrivateDataIQProvider + query + jabber:iq:private + org.jivesoftware.smackx.iqprivate.PrivateDataManager$PrivateDataIQProvider - + - query - jabber:iq:time - org.jivesoftware.smackx.time.packet.Time + time + urn:xmpp:time + org.jivesoftware.smackx.time.packet.Time @@ -209,7 +209,7 @@ http://jabber.org/protocol/ibb org.jivesoftware.smackx.bytestreams.ibb.provider.CloseIQProvider - + data http://jabber.org/protocol/ibb @@ -258,7 +258,7 @@ http://jabber.org/protocol/commands org.jivesoftware.smackx.commands.provider.AdHocCommandDataProvider$SessionExpiredError - + headers @@ -345,14 +345,14 @@ http://jabber.org/protocol/pubsub#owner org.jivesoftware.smackx.pubsub.provider.FormNodeProvider - + event http://jabber.org/protocol/pubsub#event org.jivesoftware.smackx.pubsub.provider.EventProvider - + configuration http://jabber.org/protocol/pubsub#event @@ -364,31 +364,31 @@ http://jabber.org/protocol/pubsub#event org.jivesoftware.smackx.pubsub.provider.SimpleNodeProvider - + options http://jabber.org/protocol/pubsub#event org.jivesoftware.smackx.pubsub.provider.FormNodeProvider - + items http://jabber.org/protocol/pubsub#event org.jivesoftware.smackx.pubsub.provider.ItemsProvider - + item http://jabber.org/protocol/pubsub#event org.jivesoftware.smackx.pubsub.provider.ItemProvider - + retract http://jabber.org/protocol/pubsub#event org.jivesoftware.smackx.pubsub.provider.RetractEventProvider - + purge http://jabber.org/protocol/pubsub#event @@ -401,7 +401,7 @@ http://jabber.org/protocol/nick org.jivesoftware.smackx.nick.packet.Nick$Provider - + attention diff --git a/extensions/src/main/resources/org.jivesoftware.smackx/extensions.xml b/extensions/src/main/resources/org.jivesoftware.smackx/extensions.xml index d573c7fcb..f8f43954d 100644 --- a/extensions/src/main/resources/org.jivesoftware.smackx/extensions.xml +++ b/extensions/src/main/resources/org.jivesoftware.smackx/extensions.xml @@ -10,5 +10,6 @@ org.jivesoftware.smackx.commands.AdHocCommandManager org.jivesoftware.smackx.ping.PingManager org.jivesoftware.smackx.privacy.PrivacyListManager + org.jivesoftware.smackx.time.EntityTimeManager \ No newline at end of file diff --git a/extensions/src/test/java/org/jivesoftware/smackx/InitExtensions.java b/extensions/src/test/java/org/jivesoftware/smackx/InitExtensions.java new file mode 100644 index 000000000..4d1a67265 --- /dev/null +++ b/extensions/src/test/java/org/jivesoftware/smackx/InitExtensions.java @@ -0,0 +1,26 @@ +/** + * + * Copyright 2014 Florian Schmaus + * + * 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; + +public class InitExtensions { + + static { + (new ExtensionsProviderInitializer()).initialize(); + (new ExtensionsStartupClasses()).initialize(); + } + +} diff --git a/extensions/src/test/java/org/jivesoftware/smackx/delay/provider/DelayInformationTest.java b/extensions/src/test/java/org/jivesoftware/smackx/delay/provider/DelayInformationTest.java index 3cecc500e..47641fb63 100644 --- a/extensions/src/test/java/org/jivesoftware/smackx/delay/provider/DelayInformationTest.java +++ b/extensions/src/test/java/org/jivesoftware/smackx/delay/provider/DelayInformationTest.java @@ -27,7 +27,7 @@ import java.util.GregorianCalendar; import java.util.Properties; import java.util.TimeZone; -import org.jivesoftware.smack.util.StringUtils; +import org.jivesoftware.smack.util.XmppDateTime; import org.jivesoftware.smackx.delay.packet.DelayInfo; import org.jivesoftware.smackx.delay.packet.DelayInformation; import org.jivesoftware.smackx.delay.provider.DelayInfoProvider; @@ -218,7 +218,7 @@ public class DelayInformationTest { .asString(outputProperties); delayInfo = (DelayInfo) p.parseExtension(getParser(control, "delay")); - Date controlDate = StringUtils.parseXEP0082Date("2008-06-08T09:16:20.0Z"); + Date controlDate = XmppDateTime.parseXEP0082Date("2008-06-08T09:16:20.0Z"); assertEquals(controlDate, delayInfo.getStamp()); diff --git a/extensions/src/test/java/org/jivesoftware/smackx/time/packet/TimeTest.java b/extensions/src/test/java/org/jivesoftware/smackx/time/packet/TimeTest.java new file mode 100644 index 000000000..e284f6a7d --- /dev/null +++ b/extensions/src/test/java/org/jivesoftware/smackx/time/packet/TimeTest.java @@ -0,0 +1,98 @@ +/** + * + * Copyright 2014 Florian Schmaus + * + * 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.time.packet; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.util.Calendar; +import java.util.Date; +import java.util.TimeZone; + +import org.jivesoftware.smack.DummyConnection; +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.test.util.TestUtils; +import org.jivesoftware.smack.util.PacketParserUtils; +import org.jivesoftware.smackx.InitExtensions; +import org.junit.Test; + +public class TimeTest extends InitExtensions { + + @Test + public void parseCurrentTimeTest() { + Calendar calendar = Calendar.getInstance(); + Time time = new Time(calendar); + + Date date = time.getTime(); + Date calendarDate = calendar.getTime(); + + assertEquals(calendarDate, date); + } + + @Test + public void negativeTimezoneTest() { + Calendar calendar = Calendar.getInstance(); + calendar.setTimeZone(TimeZone.getTimeZone("GMT-830")); + Time time = new Time(calendar); + + assertEquals("-8:30", time.getTzo()); + } + + @Test + public void positiveTimezoneTest() { + Calendar calendar = Calendar.getInstance(); + calendar.setTimeZone(TimeZone.getTimeZone("GMT+830")); + Time time = new Time(calendar); + + assertEquals("+8:30", time.getTzo()); + } + + @Test + public void parseTimeWithIntrospectionTest() throws Exception { + DummyConnection connection = new DummyConnection(); + + // @formatter:off + final String request = + "" + + ""; + // @formatter:on + IQ iqRequest = PacketParserUtils.parseIQ(TestUtils.getIQParser(request), connection); + assertTrue(iqRequest instanceof Time); + + // @formatter:off + final String response = + "" + + "" + + ""; + // @formatter:on + IQ iqResponse = PacketParserUtils.parseIQ(TestUtils.getIQParser(response), connection); + assertTrue(iqResponse instanceof Time); + Time time = (Time) iqResponse; + assertEquals("-06:00", time.getTzo()); + assertEquals("2006-12-19T17:58:35Z", time.getUtc()); + } +}