Author: sebb Date: Wed Feb 29 01:07:22 2012 New Revision: 1294922 URL: http://svn.apache.org/viewvc?rev=1294922&view=rev Log: NET-444 FTPTimestampParserImpl fails to parse future dates correctly on Feb 28th in a leap year
Modified: commons/proper/net/trunk/src/changes/changes.xml commons/proper/net/trunk/src/main/java/org/apache/commons/net/ftp/parser/FTPTimestampParserImpl.java commons/proper/net/trunk/src/test/java/org/apache/commons/net/ftp/parser/FTPTimestampParserImplTest.java Modified: commons/proper/net/trunk/src/changes/changes.xml URL: http://svn.apache.org/viewvc/commons/proper/net/trunk/src/changes/changes.xml?rev=1294922&r1=1294921&r2=1294922&view=diff ============================================================================== --- commons/proper/net/trunk/src/changes/changes.xml (original) +++ commons/proper/net/trunk/src/changes/changes.xml Wed Feb 29 01:07:22 2012 @@ -62,6 +62,13 @@ The <action> type attribute can be add,u --> <body> + <release version="3.2" date="TBA" description=" + "> +TBA + <action issue="NET-444" dev="sebb" type="fix"> + FTPTimestampParserImpl fails to parse future dates correctly on Feb 28th in a leap year. + </action> + </release> <release version="3.1" date="Feb 20, 2012" description=" This release fixes a few bugs and adds some new functionality (see below). It is binary compatible with previous releases Modified: commons/proper/net/trunk/src/main/java/org/apache/commons/net/ftp/parser/FTPTimestampParserImpl.java URL: http://svn.apache.org/viewvc/commons/proper/net/trunk/src/main/java/org/apache/commons/net/ftp/parser/FTPTimestampParserImpl.java?rev=1294922&r1=1294921&r2=1294922&view=diff ============================================================================== --- commons/proper/net/trunk/src/main/java/org/apache/commons/net/ftp/parser/FTPTimestampParserImpl.java (original) +++ commons/proper/net/trunk/src/main/java/org/apache/commons/net/ftp/parser/FTPTimestampParserImpl.java Wed Feb 29 01:07:22 2012 @@ -90,67 +90,58 @@ public class FTPTimestampParserImpl impl * @since 1.5 */ public Calendar parseTimestamp(String timestampStr, Calendar serverTime) throws ParseException { - Calendar now = (Calendar) serverTime.clone();// Copy this, because we may change it - now.setTimeZone(this.getServerTimeZone()); - Calendar working = (Calendar) now.clone(); + Calendar working = (Calendar) serverTime.clone(); working.setTimeZone(getServerTimeZone()); // is this needed? - ParsePosition pp = new ParsePosition(0); Date parsed = null; + if (recentDateFormat != null) { + Calendar now = (Calendar) serverTime.clone();// Copy this, because we may change it + now.setTimeZone(this.getServerTimeZone()); if (lenientFutureDates) { // add a day to "now" so that "slop" doesn't cause a date // slightly in the future to roll back a full year. (Bug 35181 => NET-83) now.add(Calendar.DATE, 1); } - parsed = recentDateFormat.parse(timestampStr, pp); - } - if (parsed != null && pp.getIndex() == timestampStr.length()) - { - working.setTime(parsed); - working.set(Calendar.YEAR, now.get(Calendar.YEAR)); - - if (working.after(now)) { - working.add(Calendar.YEAR, -1); - } - } else { // Temporarily add the current year to the short date time // to cope with short-date leap year strings. // e.g. Java's DateFormatter will assume that "Feb 29 12:00" refers to // Feb 29 1970 (an invalid date) rather than a potentially valid leap year date. // This is pretty bad hack to work around the deficiencies of the JDK date/time classes. - if (recentDateFormat != null) { - pp = new ParsePosition(0); - int year = now.get(Calendar.YEAR); - String timeStampStrPlusYear = timestampStr + " " + year; - SimpleDateFormat hackFormatter = new SimpleDateFormat(recentDateFormat.toPattern() + " yyyy", - recentDateFormat.getDateFormatSymbols()); - hackFormatter.setLenient(false); - hackFormatter.setTimeZone(recentDateFormat.getTimeZone()); - parsed = hackFormatter.parse(timeStampStrPlusYear, pp); - } - if (parsed != null && pp.getIndex() == timestampStr.length() + 5) { + String year = Integer.toString(now.get(Calendar.YEAR)); + String timeStampStrPlusYear = timestampStr + " " + year; + SimpleDateFormat hackFormatter = new SimpleDateFormat(recentDateFormat.toPattern() + " yyyy", + recentDateFormat.getDateFormatSymbols()); + hackFormatter.setLenient(false); + hackFormatter.setTimeZone(recentDateFormat.getTimeZone()); + ParsePosition pp = new ParsePosition(0); + parsed = hackFormatter.parse(timeStampStrPlusYear, pp); + // Check if we parsed the full string, if so it must have been a short date originally + if (parsed != null && pp.getIndex() == timeStampStrPlusYear.length()) { working.setTime(parsed); - } - else { - pp = new ParsePosition(0); - parsed = defaultDateFormat.parse(timestampStr, pp); - // note, length checks are mandatory for us since - // SimpleDateFormat methods will succeed if less than - // full string is matched. They will also accept, - // despite "leniency" setting, a two-digit number as - // a valid year (e.g. 22:04 will parse as 22 A.D.) - // so could mistakenly confuse an hour with a year, - // if we don't insist on full length parsing. - if (parsed != null && pp.getIndex() == timestampStr.length()) { - working.setTime(parsed); - } else { - throw new ParseException( - "Timestamp could not be parsed with older or recent DateFormat", - pp.getErrorIndex()); + if (working.after(now)) { // must have been last year instead + working.add(Calendar.YEAR, -1); } + return working; } } + + ParsePosition pp = new ParsePosition(0); + parsed = defaultDateFormat.parse(timestampStr, pp); + // note, length checks are mandatory for us since + // SimpleDateFormat methods will succeed if less than + // full string is matched. They will also accept, + // despite "leniency" setting, a two-digit number as + // a valid year (e.g. 22:04 will parse as 22 A.D.) + // so could mistakenly confuse an hour with a year, + // if we don't insist on full length parsing. + if (parsed != null && pp.getIndex() == timestampStr.length()) { + working.setTime(parsed); + } else { + throw new ParseException( + "Timestamp could not be parsed with older or recent DateFormat", + pp.getErrorIndex()); + } return working; } Modified: commons/proper/net/trunk/src/test/java/org/apache/commons/net/ftp/parser/FTPTimestampParserImplTest.java URL: http://svn.apache.org/viewvc/commons/proper/net/trunk/src/test/java/org/apache/commons/net/ftp/parser/FTPTimestampParserImplTest.java?rev=1294922&r1=1294921&r2=1294922&view=diff ============================================================================== --- commons/proper/net/trunk/src/test/java/org/apache/commons/net/ftp/parser/FTPTimestampParserImplTest.java (original) +++ commons/proper/net/trunk/src/test/java/org/apache/commons/net/ftp/parser/FTPTimestampParserImplTest.java Wed Feb 29 01:07:22 2012 @@ -97,6 +97,25 @@ public class FTPTimestampParserImplTest } } + public void testNET444() throws Exception { + FTPTimestampParserImpl parser = new FTPTimestampParserImpl(); + parser.setLenientFutureDates(true); + SimpleDateFormat sdf = new SimpleDateFormat(parser.getRecentDateFormatString()); + GregorianCalendar now = new GregorianCalendar(2012, Calendar.FEBRUARY, 28, 12, 0); + + GregorianCalendar nowplus1 = new GregorianCalendar(2012, Calendar.FEBRUARY, 28, 13, 0); + // Create a suitable short date + String future1 = sdf.format(nowplus1.getTime()); + Calendar parsed1 = parser.parseTimestamp(future1, now); + assertEquals(nowplus1.get(Calendar.YEAR), parsed1.get(Calendar.YEAR)); + + GregorianCalendar nowplus25 = new GregorianCalendar(2012, Calendar.FEBRUARY, 29, 13, 0); + // Create a suitable short date + String future25 = sdf.format(nowplus25.getTime()); + Calendar parsed25 = parser.parseTimestamp(future25, now); + assertEquals(nowplus25.get(Calendar.YEAR) - 1, parsed25.get(Calendar.YEAR)); + } + public void testParseTimestampAcrossTimeZones() { @@ -174,8 +193,8 @@ public class FTPTimestampParserImplTest fail("failed.to.parse.default"); } try { - parser.parseTimestamp("f\u00e9v 22 2002"); - fail("should.have.failed.to.parse.default"); + Calendar c = parser.parseTimestamp("f\u00e9v 22 2002"); + fail("should.have.failed.to.parse.default, but was: "+c.getTime().toString()); } catch (ParseException e) { // this is the success case }