1
0
Fork 0
mirror of https://codeberg.org/Mercury-IM/Smack synced 2024-11-26 16:22:06 +01:00

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.
This commit is contained in:
Florian Schmaus 2014-03-03 09:44:32 +01:00
parent 768700b301
commit 585e20e93e
21 changed files with 904 additions and 678 deletions

View file

@ -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<Connection> weakConnection;
public Manager(Connection connection) {
weakConnection = new WeakReference<Connection>(connection);
}
protected final Connection connection() {
return weakConnection.get();
}
}

View file

@ -196,7 +196,7 @@ public final class ProviderManager {
IQ.class.isAssignableFrom((Class<?>)provider)))) IQ.class.isAssignableFrom((Class<?>)provider))))
{ {
throw new IllegalArgumentException("Provider must be an IQProvider " + throw new IllegalArgumentException("Provider must be an IQProvider " +
"or a Class instance."); "or a Class instance sublcassing IQ.");
} }
String key = getProviderKey(elementName, namespace); String key = getProviderKey(elementName, namespace);
iqProviders.put(key, provider); iqProviders.put(key, provider);

View file

@ -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());
}
}

View file

@ -341,9 +341,9 @@ public class PacketParserUtils {
// Decide what to do when an IQ packet was not understood // Decide what to do when an IQ packet was not understood
if (iqPacket == null) { if (iqPacket == null) {
if (IQ.Type.GET == type || IQ.Type.SET == type ) { if (IQ.Type.GET == type || IQ.Type.SET == type ) {
// If the IQ stanza is of type "get" or "set" containing a child element // If the IQ stanza is of type "get" or "set" containing a child element qualified
// qualified by a namespace it does not understand, then answer an IQ of // by a namespace with no registered Smack provider, then answer an IQ of type
// type "error" with code 501 ("feature-not-implemented") // "error" with code 501 ("feature-not-implemented")
iqPacket = new IQ() { iqPacket = new IQ() {
@Override @Override
public String getChildElementXML() { public String getChildElementXML() {
@ -848,7 +848,7 @@ public class PacketParserUtils {
} }
} }
return object; return object;
} }
/** /**
* Decodes a String into an object of the specified type. If the object * Decodes a String into an object of the specified type. If the object

View file

@ -20,21 +20,9 @@ package org.jivesoftware.smack.util;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException; 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.Random;
import java.util.TimeZone;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/** /**
* A collection of utility methods for String objects. * A collection of utility methods for String objects.
@ -42,234 +30,12 @@ import java.util.regex.Pattern;
public class StringUtils { public class StringUtils {
private static final Logger LOGGER = Logger.getLogger(StringUtils.class.getName()); 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.
* <p>
* 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<PatternCouplings> couplings = new ArrayList<PatternCouplings>();
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 = "&quot;".toCharArray(); private static final char[] QUOTE_ENCODE = "&quot;".toCharArray();
private static final char[] APOS_ENCODE = "&apos;".toCharArray(); private static final char[] APOS_ENCODE = "&apos;".toCharArray();
private static final char[] AMP_ENCODE = "&amp;".toCharArray(); private static final char[] AMP_ENCODE = "&amp;".toCharArray();
private static final char[] LT_ENCODE = "&lt;".toCharArray(); private static final char[] LT_ENCODE = "&lt;".toCharArray();
private static final char[] GT_ENCODE = "&gt;".toCharArray(); private static final char[] GT_ENCODE = "&gt;".toCharArray();
/**
* Parses the given date string in the <a href="http://xmpp.org/extensions/xep-0082.html">XEP-0082 - XMPP Date and Time Profiles</a>.
*
* @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 <a href="http://xmpp.org/extensions/xep-0082.html">XEP-0082 - XMPP Date and Time Profiles</a>
* or <a href="http://xmpp.org/extensions/xep-0091.html">XEP-0091 - Legacy Delayed Delivery</a> format.
* <p>
* 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<Calendar> 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<Calendar> filterDatesBefore(Calendar now, Calendar... dates) {
List<Calendar> result = new ArrayList<Calendar>();
for (Calendar calendar : dates) {
if (calendar != null && calendar.before(now)) {
result.add(calendar);
}
}
return result;
}
private static Calendar determineNearestDate(final Calendar now, List<Calendar> dates) {
Collections.sort(dates, new Comparator<Calendar>() {
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 * Returns the name portion of a XMPP address. For example, for the
* address "matt@jivesoftware.com/Smack", "matt" would be returned. If no * address "matt@jivesoftware.com/Smack", "matt" would be returned. If no
@ -804,38 +570,4 @@ public class StringUtils {
return new String(randBuffer); 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");
}
}
}
} }

View file

@ -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<PatternCouplings> couplings = new ArrayList<PatternCouplings>();
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 <a
* href="http://xmpp.org/extensions/xep-0082.html">XEP-0082 - XMPP Date and Time Profiles</a>.
*
* @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 <a
* href="http://xmpp.org/extensions/xep-0082.html">XEP-0082 - XMPP Date and Time Profiles</a> or
* <a href="http://xmpp.org/extensions/xep-0091.html">XEP-0091 - Legacy Delayed Delivery</a>
* format.
* <p>
* 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<Calendar> 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<Calendar> filterDatesBefore(Calendar now, Calendar... dates) {
List<Calendar> result = new ArrayList<Calendar>();
for (Calendar calendar : dates) {
if (calendar != null && calendar.before(now)) {
result.add(calendar);
}
}
return result;
}
private static Calendar determineNearestDate(final Calendar now, List<Calendar> dates) {
Collections.sort(dates, new Comparator<Calendar>() {
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;
}
}
}

View file

@ -22,11 +22,6 @@ import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail; 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; 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/registred"));
assertEquals(error, result, StringUtils.parseServer("user@yahoo.myjabber.net")); 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");
}
} }

View file

@ -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");
}
}

View file

@ -18,7 +18,7 @@ package org.jivesoftware.smackx.delay.packet;
import java.util.Date; 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 * 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("<").append(getElementName()).append(" xmlns=\"").append(getNamespace()).append(
"\""); "\"");
buf.append(" stamp=\""); buf.append(" stamp=\"");
buf.append(StringUtils.formatXEP0082Date(getStamp())); buf.append(XmppDateTime.formatXEP0082Date(getStamp()));
buf.append("\""); buf.append("\"");
if (getFrom() != null && getFrom().length() > 0) { if (getFrom() != null && getFrom().length() > 0) {
buf.append(" from=\"").append(getFrom()).append("\""); buf.append(" from=\"").append(getFrom()).append("\"");

View file

@ -21,7 +21,7 @@ import java.util.Date;
import org.jivesoftware.smack.packet.PacketExtension; import org.jivesoftware.smack.packet.PacketExtension;
import org.jivesoftware.smack.provider.PacketExtensionProvider; 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.jivesoftware.smackx.delay.packet.DelayInformation;
import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParser;
@ -38,7 +38,7 @@ public class DelayInformationProvider implements PacketExtensionProvider {
Date stamp = null; Date stamp = null;
try { try {
stamp = StringUtils.parseDate(stampString); stamp = XmppDateTime.parseDate(stampString);
} }
catch (ParseException parseExc) { catch (ParseException parseExc) {
/* /*

View file

@ -645,6 +645,19 @@ public class ServiceDiscoveryManager {
connection.createPacketCollectorAndSend(discoverItems).nextResultOrThrow(); 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 * Entity Capabilities
*/ */

View file

@ -23,7 +23,7 @@ import java.util.Date;
import java.util.Iterator; import java.util.Iterator;
import java.util.UnknownFormatConversionException; 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.Form;
import org.jivesoftware.smackx.xdata.FormField; import org.jivesoftware.smackx.xdata.FormField;
import org.jivesoftware.smackx.xdata.packet.DataForm; import org.jivesoftware.smackx.xdata.packet.DataForm;
@ -128,7 +128,7 @@ public class SubscribeForm extends Form
String dateTime = getFieldValue(SubscribeOptionFields.expire); String dateTime = getFieldValue(SubscribeOptionFields.expire);
try try
{ {
return StringUtils.parseDate(dateTime); return XmppDateTime.parseDate(dateTime);
} }
catch (ParseException e) catch (ParseException e)
{ {
@ -146,7 +146,7 @@ public class SubscribeForm extends Form
public void setExpiry(Date expire) public void setExpiry(Date expire)
{ {
addField(SubscribeOptionFields.expire, FormField.TYPE_TEXT_SINGLE); addField(SubscribeOptionFields.expire, FormField.TYPE_TEXT_SINGLE);
setAnswer(SubscribeOptionFields.expire.getFieldName(), StringUtils.formatXEP0082Date(expire)); setAnswer(SubscribeOptionFields.expire.getFieldName(), XmppDateTime.formatXEP0082Date(expire));
} }
/** /**

View file

@ -21,6 +21,7 @@ import java.util.Date;
import org.jivesoftware.smack.packet.IQ; import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.packet.PacketExtension; import org.jivesoftware.smack.packet.PacketExtension;
import org.jivesoftware.smack.util.StringUtils; import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smack.util.XmppDateTime;
import org.jivesoftware.smackx.xdata.packet.DataForm; import org.jivesoftware.smackx.xdata.packet.DataForm;
/** /**
@ -345,7 +346,7 @@ public class StreamInitiation extends IQ {
} }
if (getDate() != null) { if (getDate() != null) {
buffer.append("date=\"").append(StringUtils.formatXEP0082Date(date)).append("\" "); buffer.append("date=\"").append(XmppDateTime.formatXEP0082Date(date)).append("\" ");
} }
if (getHash() != null) { if (getHash() != null) {

View file

@ -23,7 +23,7 @@ import java.util.logging.Logger;
import org.jivesoftware.smack.packet.IQ; import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.provider.IQProvider; 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;
import org.jivesoftware.smackx.si.packet.StreamInitiation.File; import org.jivesoftware.smackx.si.packet.StreamInitiation.File;
import org.jivesoftware.smackx.xdata.packet.DataForm; import org.jivesoftware.smackx.xdata.packet.DataForm;
@ -98,7 +98,7 @@ public class StreamInitiationProvider implements IQProvider {
Date fileDate = new Date(); Date fileDate = new Date();
if (date != null) { if (date != null) {
try { try {
fileDate = StringUtils.parseDate(date); fileDate = XmppDateTime.parseDate(date);
} catch (ParseException e) { } catch (ParseException e) {
// couldn't parse date, use current date-time // couldn't parse date, use current date-time
} }

View file

@ -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<Connection, EntityTimeManager> INSTANCES = new WeakHashMap<Connection, EntityTimeManager>();
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;
}
}

View file

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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 * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.jivesoftware.smackx.time.packet; package org.jivesoftware.smackx.time.packet;
import org.jivesoftware.smack.packet.IQ; 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.Calendar;
import java.util.Date; import java.util.Date;
import java.util.TimeZone;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; 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 * 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 * 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. * 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:
* *
* <pre> * @see http://www.xmpp.org/extensions/xep-0202.html
* // Request the time from a remote user. * @author Florian Schmaus
* 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...
* }</pre><p>
*
* Warning: this is an non-standard protocol documented by
* <a href="http://www.xmpp.org/extensions/xep-0090.html">XEP-0090</a>. Because this is a
* non-standard protocol, it is subject to change.
*
* @author Matt Tucker
*/ */
public class Time extends IQ { 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 final Logger LOGGER = Logger.getLogger(Time.class.getName());
private static SimpleDateFormat utcFormat = new SimpleDateFormat("yyyyMMdd'T'HH:mm:ss"); private String utc;
private static DateFormat displayFormat = DateFormat.getDateTimeInstance(); private String tzo;
private String utc = null;
private String tz = null;
private String display = null;
/**
* Creates a new Time instance with empty values for all fields.
*/
public Time() { public Time() {
setType(Type.GET);
} }
/** /**
@ -83,12 +53,9 @@ public class Time extends IQ {
* @param cal the time value. * @param cal the time value.
*/ */
public Time(Calendar cal) { public Time(Calendar cal) {
TimeZone timeZone = cal.getTimeZone(); tzo = XmppDateTime.asString(cal.getTimeZone());
tz = cal.getTimeZone().getID();
display = displayFormat.format(cal.getTime());
// Convert local time to the UTC time. // Convert local time to the UTC time.
utc = utcFormat.format(new Date( utc = XmppDateTime.formatXEP0082Date(cal.getTime());
cal.getTimeInMillis() - timeZone.getOffset(cal.getTimeInMillis())));
} }
/** /**
@ -102,11 +69,7 @@ public class Time extends IQ {
} }
Date date = null; Date date = null;
try { try {
Calendar cal = Calendar.getInstance(); date = XmppDateTime.parseDate(utc);
// Convert the UTC time to local time.
cal.setTime(new Date(utcFormat.parse(utc).getTime() +
cal.getTimeZone().getOffset(cal.getTimeInMillis())));
date = cal.getTime();
} }
catch (Exception e) { catch (Exception e) {
LOGGER.log(Level.SEVERE, "Error getting local time", 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. * @param time the current local time.
*/ */
public void setTime(Date 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. * @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. * @param utc the time using a formatted String.
*/ */
public void setUtc(String utc) { public void setUtc(String utc) {
this.utc = utc; this.utc = utc;
} }
/** /**
@ -149,50 +108,34 @@ public class Time extends IQ {
* *
* @return the time zone. * @return the time zone.
*/ */
public String getTz() { public String getTzo() {
return tz; 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) { public void setTzo(String tzo) {
this.tz = tz; this.tzo = tzo;
} }
/** public static Time createResponse(Packet request) {
* Returns the local (non-utc) time in human-friendly format. Time time = new Time(Calendar.getInstance());
* time.setType(Type.RESULT);
* @return the local time in human-friendly format. time.setTo(request.getFrom());
*/ return time;
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 String getChildElementXML() { public String getChildElementXML() {
StringBuilder buf = new StringBuilder(); StringBuilder buf = new StringBuilder();
buf.append("<query xmlns=\"jabber:iq:time\">"); buf.append("<" + ELEMENT + " xmlns='" + NAMESPACE + "'>");
if (utc != null) { if (utc != null) {
buf.append("<utc>").append(utc).append("</utc>"); buf.append("<utc>").append(utc).append("</utc>");
buf.append("<tzo>").append(tzo).append("</tzo>");
} }
if (tz != null) { buf.append("</" + ELEMENT + ">");
buf.append("<tz>").append(tz).append("</tz>");
}
if (display != null) {
buf.append("<display>").append(display).append("</display>");
}
buf.append("</query>");
return buf.toString(); return buf.toString();
} }
} }

View file

@ -11,8 +11,8 @@
<!-- Time --> <!-- Time -->
<iqProvider> <iqProvider>
<elementName>query</elementName> <elementName>time</elementName>
<namespace>jabber:iq:time</namespace> <namespace>urn:xmpp:time</namespace>
<className>org.jivesoftware.smackx.time.packet.Time</className> <className>org.jivesoftware.smackx.time.packet.Time</className>
</iqProvider> </iqProvider>

View file

@ -10,5 +10,6 @@
<className>org.jivesoftware.smackx.commands.AdHocCommandManager</className> <className>org.jivesoftware.smackx.commands.AdHocCommandManager</className>
<className>org.jivesoftware.smackx.ping.PingManager</className> <className>org.jivesoftware.smackx.ping.PingManager</className>
<className>org.jivesoftware.smackx.privacy.PrivacyListManager</className> <className>org.jivesoftware.smackx.privacy.PrivacyListManager</className>
<className>org.jivesoftware.smackx.time.EntityTimeManager</className>
</startupClasses> </startupClasses>
</smack> </smack>

View file

@ -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();
}
}

View file

@ -27,7 +27,7 @@ import java.util.GregorianCalendar;
import java.util.Properties; import java.util.Properties;
import java.util.TimeZone; 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.DelayInfo;
import org.jivesoftware.smackx.delay.packet.DelayInformation; import org.jivesoftware.smackx.delay.packet.DelayInformation;
import org.jivesoftware.smackx.delay.provider.DelayInfoProvider; import org.jivesoftware.smackx.delay.provider.DelayInfoProvider;
@ -218,7 +218,7 @@ public class DelayInformationTest {
.asString(outputProperties); .asString(outputProperties);
delayInfo = (DelayInfo) p.parseExtension(getParser(control, "delay")); 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()); assertEquals(controlDate, delayInfo.getStamp());

View file

@ -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 =
"<iq type='get'"
+ "from='romeo@montague.net/orchard'"
+ "to='juliet@capulet.com/balcony'"
+ "id='time_1'>"
+ "<time xmlns='urn:xmpp:time'/>"
+ "</iq>";
// @formatter:on
IQ iqRequest = PacketParserUtils.parseIQ(TestUtils.getIQParser(request), connection);
assertTrue(iqRequest instanceof Time);
// @formatter:off
final String response =
"<iq type='result'"
+ "from='juliet@capulet.com/balcony'"
+ "to='romeo@montague.net/orchard'"
+ "id='time_1'>"
+ "<time xmlns='urn:xmpp:time'>"
+ "<tzo>-06:00</tzo>"
+ "<utc>2006-12-19T17:58:35Z</utc>"
+ "</time>"
+ "</iq>";
// @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());
}
}