001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018package org.apache.commons.net.ftp.parser; 019 020import java.text.DateFormatSymbols; 021import java.text.ParseException; 022import java.text.ParsePosition; 023import java.text.SimpleDateFormat; 024import java.util.Calendar; 025import java.util.Date; 026import java.util.TimeZone; 027 028import org.apache.commons.net.ftp.Configurable; 029import org.apache.commons.net.ftp.FTPClientConfig; 030 031/** 032 * Default implementation of the {@link FTPTimestampParser FTPTimestampParser} 033 * interface also implements the {@link org.apache.commons.net.ftp.Configurable Configurable} 034 * interface to allow the parsing to be configured from the outside. 035 * 036 * @see ConfigurableFTPFileEntryParserImpl 037 * @since 1.4 038 */ 039public class FTPTimestampParserImpl implements 040 FTPTimestampParser, Configurable 041{ 042 043 044 private SimpleDateFormat defaultDateFormat; 045 private SimpleDateFormat recentDateFormat; 046 private boolean lenientFutureDates = false; 047 048 049 /** 050 * The only constructor for this class. 051 */ 052 public FTPTimestampParserImpl() { 053 setDefaultDateFormat(DEFAULT_SDF); 054 setRecentDateFormat(DEFAULT_RECENT_SDF); 055 } 056 057 /** 058 * Implements the one {@link FTPTimestampParser#parseTimestamp(String) method} 059 * in the {@link FTPTimestampParser FTPTimestampParser} interface 060 * according to this algorithm: 061 * 062 * If the recentDateFormat member has been defined, try to parse the 063 * supplied string with that. If that parse fails, or if the recentDateFormat 064 * member has not been defined, attempt to parse with the defaultDateFormat 065 * member. If that fails, throw a ParseException. 066 * 067 * This method assumes that the server time is the same as the local time. 068 * 069 * @see FTPTimestampParserImpl#parseTimestamp(String, Calendar) 070 * 071 * @param timestampStr The timestamp to be parsed 072 */ 073// @Override 074 public Calendar parseTimestamp(String timestampStr) throws ParseException { 075 Calendar now = Calendar.getInstance(); 076 return parseTimestamp(timestampStr, now); 077 } 078 079 /** 080 * If the recentDateFormat member has been defined, try to parse the 081 * supplied string with that. If that parse fails, or if the recentDateFormat 082 * member has not been defined, attempt to parse with the defaultDateFormat 083 * member. If that fails, throw a ParseException. 084 * 085 * This method allows a {@link Calendar} instance to be passed in which represents the 086 * current (system) time. 087 * 088 * @see FTPTimestampParser#parseTimestamp(String) 089 * @param timestampStr The timestamp to be parsed 090 * @param serverTime The current time for the server 091 * @since 1.5 092 */ 093 public Calendar parseTimestamp(String timestampStr, Calendar serverTime) throws ParseException { 094 Calendar working = (Calendar) serverTime.clone(); 095 working.setTimeZone(getServerTimeZone()); // is this needed? 096 097 Date parsed = null; 098 099 if (recentDateFormat != null) { 100 Calendar now = (Calendar) serverTime.clone();// Copy this, because we may change it 101 now.setTimeZone(this.getServerTimeZone()); 102 if (lenientFutureDates) { 103 // add a day to "now" so that "slop" doesn't cause a date 104 // slightly in the future to roll back a full year. (Bug 35181 => NET-83) 105 now.add(Calendar.DATE, 1); 106 } 107 // The Java SimpleDateFormat class uses the epoch year 1970 if not present in the input 108 // As 1970 was not a leap year, it cannot parse "Feb 29" correctly. 109 // Java 1.5+ returns Mar 1 1970 110 // Temporarily add the current year to the short date time 111 // to cope with short-date leap year strings. 112 // Since Feb 29 is more that 6 months from the end of the year, this should be OK for 113 // all instances of short dates which are +- 6 months from current date. 114 // TODO this won't always work for systems that use short dates +0/-12months 115 // e.g. if today is Jan 1 2001 and the short date is Feb 29 116 String year = Integer.toString(now.get(Calendar.YEAR)); 117 String timeStampStrPlusYear = timestampStr + " " + year; 118 SimpleDateFormat hackFormatter = new SimpleDateFormat(recentDateFormat.toPattern() + " yyyy", 119 recentDateFormat.getDateFormatSymbols()); 120 hackFormatter.setLenient(false); 121 hackFormatter.setTimeZone(recentDateFormat.getTimeZone()); 122 ParsePosition pp = new ParsePosition(0); 123 parsed = hackFormatter.parse(timeStampStrPlusYear, pp); 124 // Check if we parsed the full string, if so it must have been a short date originally 125 if (parsed != null && pp.getIndex() == timeStampStrPlusYear.length()) { 126 working.setTime(parsed); 127 if (working.after(now)) { // must have been last year instead 128 working.add(Calendar.YEAR, -1); 129 } 130 return working; 131 } 132 } 133 134 ParsePosition pp = new ParsePosition(0); 135 parsed = defaultDateFormat.parse(timestampStr, pp); 136 // note, length checks are mandatory for us since 137 // SimpleDateFormat methods will succeed if less than 138 // full string is matched. They will also accept, 139 // despite "leniency" setting, a two-digit number as 140 // a valid year (e.g. 22:04 will parse as 22 A.D.) 141 // so could mistakenly confuse an hour with a year, 142 // if we don't insist on full length parsing. 143 if (parsed != null && pp.getIndex() == timestampStr.length()) { 144 working.setTime(parsed); 145 } else { 146 throw new ParseException( 147 "Timestamp '"+timestampStr+"' could not be parsed using a server time of " 148 +serverTime.getTime().toString(), 149 pp.getErrorIndex()); 150 } 151 return working; 152 } 153 154 /** 155 * @return Returns the defaultDateFormat. 156 */ 157 public SimpleDateFormat getDefaultDateFormat() { 158 return defaultDateFormat; 159 } 160 /** 161 * @return Returns the defaultDateFormat pattern string. 162 */ 163 public String getDefaultDateFormatString() { 164 return defaultDateFormat.toPattern(); 165 } 166 /** 167 * @param defaultDateFormat The defaultDateFormat to be set. 168 */ 169 private void setDefaultDateFormat(String format) { 170 if (format != null) { 171 this.defaultDateFormat = new SimpleDateFormat(format); 172 this.defaultDateFormat.setLenient(false); 173 } 174 } 175 /** 176 * @return Returns the recentDateFormat. 177 */ 178 public SimpleDateFormat getRecentDateFormat() { 179 return recentDateFormat; 180 } 181 /** 182 * @return Returns the recentDateFormat. 183 */ 184 public String getRecentDateFormatString() { 185 return recentDateFormat.toPattern(); 186 } 187 /** 188 * @param recentDateFormat The recentDateFormat to set. 189 */ 190 private void setRecentDateFormat(String format) { 191 if (format != null) { 192 this.recentDateFormat = new SimpleDateFormat(format); 193 this.recentDateFormat.setLenient(false); 194 } 195 } 196 197 /** 198 * @return returns an array of 12 strings representing the short 199 * month names used by this parse. 200 */ 201 public String[] getShortMonths() { 202 return defaultDateFormat.getDateFormatSymbols().getShortMonths(); 203 } 204 205 206 /** 207 * @return Returns the serverTimeZone used by this parser. 208 */ 209 public TimeZone getServerTimeZone() { 210 return this.defaultDateFormat.getTimeZone(); 211 } 212 /** 213 * sets a TimeZone represented by the supplied ID string into all 214 * of the parsers used by this server. 215 * @param serverTimeZone Time Id java.util.TimeZone id used by 216 * the ftp server. If null the client's local time zone is assumed. 217 */ 218 private void setServerTimeZone(String serverTimeZoneId) { 219 TimeZone serverTimeZone = TimeZone.getDefault(); 220 if (serverTimeZoneId != null) { 221 serverTimeZone = TimeZone.getTimeZone(serverTimeZoneId); 222 } 223 this.defaultDateFormat.setTimeZone(serverTimeZone); 224 if (this.recentDateFormat != null) { 225 this.recentDateFormat.setTimeZone(serverTimeZone); 226 } 227 } 228 229 /** 230 * Implementation of the {@link Configurable Configurable} 231 * interface. Configures this <code>FTPTimestampParser</code> according 232 * to the following logic: 233 * <p> 234 * Set up the {@link FTPClientConfig#setDefaultDateFormatStr(java.lang.String) defaultDateFormat} 235 * and optionally the {@link FTPClientConfig#setRecentDateFormatStr(String) recentDateFormat} 236 * to values supplied in the config based on month names configured as follows: 237 * </p><p><ul> 238 * <li>If a {@link FTPClientConfig#setShortMonthNames(String) shortMonthString} 239 * has been supplied in the <code>config</code>, use that to parse parse timestamps.</li> 240 * <li>Otherwise, if a {@link FTPClientConfig#setServerLanguageCode(String) serverLanguageCode} 241 * has been supplied in the <code>config</code>, use the month names represented 242 * by that {@link FTPClientConfig#lookupDateFormatSymbols(String) language} 243 * to parse timestamps.</li> 244 * <li>otherwise use default English month names</li> 245 * </ul></p><p> 246 * Finally if a {@link org.apache.commons.net.ftp.FTPClientConfig#setServerTimeZoneId(String) serverTimeZoneId} 247 * has been supplied via the config, set that into all date formats that have 248 * been configured. 249 * </p> 250 */ 251// @Override 252 public void configure(FTPClientConfig config) { 253 DateFormatSymbols dfs = null; 254 255 String languageCode = config.getServerLanguageCode(); 256 String shortmonths = config.getShortMonthNames(); 257 if (shortmonths != null) { 258 dfs = FTPClientConfig.getDateFormatSymbols(shortmonths); 259 } else if (languageCode != null) { 260 dfs = FTPClientConfig.lookupDateFormatSymbols(languageCode); 261 } else { 262 dfs = FTPClientConfig.lookupDateFormatSymbols("en"); 263 } 264 265 266 String recentFormatString = config.getRecentDateFormatStr(); 267 if (recentFormatString == null) { 268 this.recentDateFormat = null; 269 } else { 270 this.recentDateFormat = new SimpleDateFormat(recentFormatString, dfs); 271 this.recentDateFormat.setLenient(false); 272 } 273 274 String defaultFormatString = config.getDefaultDateFormatStr(); 275 if (defaultFormatString == null) { 276 throw new IllegalArgumentException("defaultFormatString cannot be null"); 277 } 278 this.defaultDateFormat = new SimpleDateFormat(defaultFormatString, dfs); 279 this.defaultDateFormat.setLenient(false); 280 281 setServerTimeZone(config.getServerTimeZoneId()); 282 283 this.lenientFutureDates = config.isLenientFutureDates(); 284 } 285 /** 286 * @return Returns the lenientFutureDates. 287 */ 288 boolean isLenientFutureDates() { 289 return lenientFutureDates; 290 } 291 /** 292 * @param lenientFutureDates The lenientFutureDates to set. 293 */ 294 void setLenientFutureDates(boolean lenientFutureDates) { 295 this.lenientFutureDates = lenientFutureDates; 296 } 297}