1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.commons.net.ftp.parser;
19
20 import java.text.DateFormatSymbols;
21 import java.text.ParseException;
22 import java.text.ParsePosition;
23 import java.text.SimpleDateFormat;
24 import java.util.Calendar;
25 import java.util.Date;
26 import java.util.TimeZone;
27
28 import org.apache.commons.net.ftp.Configurable;
29 import org.apache.commons.net.ftp.FTPClientConfig;
30
31
32
33
34
35
36
37
38 public class FTPTimestampParserImpl implements FTPTimestampParser, Configurable {
39
40
41
42
43
44
45
46 private static final int[] CALENDAR_UNITS = { Calendar.MILLISECOND, Calendar.SECOND, Calendar.MINUTE, Calendar.HOUR_OF_DAY, Calendar.DAY_OF_MONTH,
47 Calendar.MONTH, Calendar.YEAR };
48
49
50
51
52 private static int getEntry(final SimpleDateFormat dateFormat) {
53 if (dateFormat == null) {
54 return 0;
55 }
56 final String FORMAT_CHARS = "SsmHdM";
57 final String pattern = dateFormat.toPattern();
58 for (final char ch : FORMAT_CHARS.toCharArray()) {
59 if (pattern.indexOf(ch) != -1) {
60 switch (ch) {
61 case 'S':
62 return indexOf(Calendar.MILLISECOND);
63 case 's':
64 return indexOf(Calendar.SECOND);
65 case 'm':
66 return indexOf(Calendar.MINUTE);
67 case 'H':
68 return indexOf(Calendar.HOUR_OF_DAY);
69 case 'd':
70 return indexOf(Calendar.DAY_OF_MONTH);
71 case 'M':
72 return indexOf(Calendar.MONTH);
73 }
74 }
75 }
76 return 0;
77 }
78
79
80
81
82 private static int indexOf(final int calendarUnit) {
83 int i;
84 for (i = 0; i < CALENDAR_UNITS.length; i++) {
85 if (calendarUnit == CALENDAR_UNITS[i]) {
86 return i;
87 }
88 }
89 return 0;
90 }
91
92
93
94
95
96 private static void setPrecision(final int index, final Calendar working) {
97 if (index <= 0) {
98 return;
99 }
100 final int field = CALENDAR_UNITS[index - 1];
101
102
103 final int value = working.get(field);
104 if (value != 0) {
105
106 } else {
107 working.clear(field);
108 }
109 }
110
111
112 private SimpleDateFormat defaultDateFormat;
113
114
115 private int defaultDateSmallestUnitIndex;
116
117
118 private SimpleDateFormat recentDateFormat;
119
120
121 private int recentDateSmallestUnitIndex;
122
123 private boolean lenientFutureDates;
124
125
126
127
128 public FTPTimestampParserImpl() {
129 setDefaultDateFormat(DEFAULT_SDF, null);
130 setRecentDateFormat(DEFAULT_RECENT_SDF, null);
131 }
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151 @Override
152 public void configure(final FTPClientConfig config) {
153 DateFormatSymbols dfs;
154
155 final String languageCode = config.getServerLanguageCode();
156 final String shortmonths = config.getShortMonthNames();
157 if (shortmonths != null) {
158 dfs = FTPClientConfig.getDateFormatSymbols(shortmonths);
159 } else if (languageCode != null) {
160 dfs = FTPClientConfig.lookupDateFormatSymbols(languageCode);
161 } else {
162 dfs = FTPClientConfig.lookupDateFormatSymbols("en");
163 }
164
165 final String recentFormatString = config.getRecentDateFormatStr();
166 setRecentDateFormat(recentFormatString, dfs);
167
168 final String defaultFormatString = config.getDefaultDateFormatStr();
169 if (defaultFormatString == null) {
170 throw new IllegalArgumentException("defaultFormatString cannot be null");
171 }
172 setDefaultDateFormat(defaultFormatString, dfs);
173
174 setServerTimeZone(config.getServerTimeZoneId());
175
176 this.lenientFutureDates = config.isLenientFutureDates();
177 }
178
179
180
181
182 public SimpleDateFormat getDefaultDateFormat() {
183 return defaultDateFormat;
184 }
185
186
187
188
189 public String getDefaultDateFormatString() {
190 return defaultDateFormat.toPattern();
191 }
192
193
194
195
196 public SimpleDateFormat getRecentDateFormat() {
197 return recentDateFormat;
198 }
199
200
201
202
203 public String getRecentDateFormatString() {
204 return recentDateFormat.toPattern();
205 }
206
207
208
209
210 public TimeZone getServerTimeZone() {
211 return this.defaultDateFormat.getTimeZone();
212 }
213
214
215
216
217 public String[] getShortMonths() {
218 return defaultDateFormat.getDateFormatSymbols().getShortMonths();
219 }
220
221
222
223
224 boolean isLenientFutureDates() {
225 return lenientFutureDates;
226 }
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242 @Override
243 public Calendar parseTimestamp(final String timestampStr) throws ParseException {
244 final Calendar now = Calendar.getInstance();
245 return parseTimestamp(timestampStr, now);
246 }
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261 public Calendar parseTimestamp(final String timestampStr, final Calendar serverTime) throws ParseException {
262 final Calendar working = (Calendar) serverTime.clone();
263 working.setTimeZone(getServerTimeZone());
264
265 Date parsed;
266
267 if (recentDateFormat != null) {
268 final Calendar now = (Calendar) serverTime.clone();
269 now.setTimeZone(this.getServerTimeZone());
270 if (lenientFutureDates) {
271
272
273 now.add(Calendar.DAY_OF_MONTH, 1);
274 }
275
276
277
278
279
280
281
282
283
284 final String year = Integer.toString(now.get(Calendar.YEAR));
285 final String timeStampStrPlusYear = timestampStr + " " + year;
286 final SimpleDateFormat hackFormatter = new SimpleDateFormat(recentDateFormat.toPattern() + " yyyy", recentDateFormat.getDateFormatSymbols());
287 hackFormatter.setLenient(false);
288 hackFormatter.setTimeZone(recentDateFormat.getTimeZone());
289 final ParsePosition pp = new ParsePosition(0);
290 parsed = hackFormatter.parse(timeStampStrPlusYear, pp);
291
292 if (parsed != null && pp.getIndex() == timeStampStrPlusYear.length()) {
293 working.setTime(parsed);
294 if (working.after(now)) {
295 working.add(Calendar.YEAR, -1);
296 }
297 setPrecision(recentDateSmallestUnitIndex, working);
298 return working;
299 }
300 }
301
302 final ParsePosition pp = new ParsePosition(0);
303 parsed = defaultDateFormat.parse(timestampStr, pp);
304
305
306
307
308
309
310
311 if (parsed == null || pp.getIndex() != timestampStr.length()) {
312 throw new ParseException("Timestamp '" + timestampStr + "' could not be parsed using a server time of " + serverTime.getTime().toString(),
313 pp.getErrorIndex());
314 }
315 working.setTime(parsed);
316 setPrecision(defaultDateSmallestUnitIndex, working);
317 return working;
318 }
319
320
321
322
323
324 private void setDefaultDateFormat(final String format, final DateFormatSymbols dfs) {
325 if (format != null) {
326 if (dfs != null) {
327 this.defaultDateFormat = new SimpleDateFormat(format, dfs);
328 } else {
329 this.defaultDateFormat = new SimpleDateFormat(format);
330 }
331 this.defaultDateFormat.setLenient(false);
332 } else {
333 this.defaultDateFormat = null;
334 }
335 this.defaultDateSmallestUnitIndex = getEntry(this.defaultDateFormat);
336 }
337
338
339
340
341 void setLenientFutureDates(final boolean lenientFutureDates) {
342 this.lenientFutureDates = lenientFutureDates;
343 }
344
345
346
347
348
349 private void setRecentDateFormat(final String format, final DateFormatSymbols dfs) {
350 if (format != null) {
351 if (dfs != null) {
352 this.recentDateFormat = new SimpleDateFormat(format, dfs);
353 } else {
354 this.recentDateFormat = new SimpleDateFormat(format);
355 }
356 this.recentDateFormat.setLenient(false);
357 } else {
358 this.recentDateFormat = null;
359 }
360 this.recentDateSmallestUnitIndex = getEntry(this.recentDateFormat);
361 }
362
363
364
365
366
367
368 private void setServerTimeZone(final String serverTimeZoneId) {
369 TimeZone serverTimeZone = TimeZone.getDefault();
370 if (serverTimeZoneId != null) {
371 serverTimeZone = TimeZone.getTimeZone(serverTimeZoneId);
372 }
373 this.defaultDateFormat.setTimeZone(serverTimeZone);
374 if (this.recentDateFormat != null) {
375 this.recentDateFormat.setTimeZone(serverTimeZone);
376 }
377 }
378 }