Index: src/java/org/apache/commons/net/ftp/parser/FTPTimestampParserImpl.java
===================================================================
--- src/java/org/apache/commons/net/ftp/parser/FTPTimestampParserImpl.java.orig	2005-12-03 17:05:48.000000000 +0100
+++ src/java/org/apache/commons/net/ftp/parser/FTPTimestampParserImpl.java	2012-03-13 10:30:30.520993343 +0100
@@ -67,44 +67,74 @@
 	 * 
 	 */
 	public Calendar parseTimestamp(String timestampStr) throws ParseException {
-		Calendar now = Calendar.getInstance();
-		now.setTimeZone(this.getServerTimeZone());
-		
-		Calendar working = Calendar.getInstance();
-		working.setTimeZone(this.getServerTimeZone());
-		ParsePosition pp = new ParsePosition(0);
+            Calendar now = Calendar.getInstance();
+            return parseTimestamp(timestampStr, now);
+        }
 
-		Date parsed = null;
-		if (this.recentDateFormat != null) {
-			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 {
-			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.getIndex());
-			}
-		}
-		return working;
-	}
+        /**
+         * If the recentDateFormat member has been defined, try to parse the
+         * supplied string with that.  If that parse fails, or if the recentDateFormat
+         * member has not been defined, attempt to parse with the defaultDateFormat
+         * member.  If that fails, throw a ParseException.
+         *
+         * This method allows a {@link Calendar} instance to be passed in which represents the
+         * current (system) time.
+         *
+         * @see FTPTimestampParser#parseTimestamp(String)
+         * @param timestampStr The timestamp to be parsed
+         * @param serverTime The current time for the server
+         * @since 1.5
+         */
+        public Calendar parseTimestamp(String timestampStr, Calendar serverTime) throws ParseException {
+            Calendar working = (Calendar) serverTime.clone();
+            working.setTimeZone(getServerTimeZone()); // is this needed?
+
+            Date parsed = null;
+
+            if (recentDateFormat != null) {
+                Calendar now = (Calendar) serverTime.clone();// Copy this, because we may change it
+                now.setTimeZone(this.getServerTimeZone());
+                // 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.
+                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);
+                    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;
+        }
 
 	/**
 	 * @return Returns the defaultDateFormat.
