1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.net.ftp.parser;
18
19 import static org.junit.jupiter.api.Assertions.assertEquals;
20 import static org.junit.jupiter.api.Assertions.assertThrows;
21 import static org.junit.jupiter.api.Assertions.fail;
22
23 import java.text.Format;
24 import java.text.ParseException;
25 import java.text.SimpleDateFormat;
26 import java.util.Calendar;
27 import java.util.Date;
28 import java.util.GregorianCalendar;
29 import java.util.Locale;
30 import java.util.TimeZone;
31
32 import org.apache.commons.net.ftp.FTPClientConfig;
33 import org.junit.jupiter.api.Disabled;
34 import org.junit.jupiter.api.Test;
35
36
37
38
39 public class FTPTimestampParserImplTest {
40
41 private static final int TWO_HOURS_OF_MILLISECONDS = 2 * 60 * 60 * 1000;
42
43
44
45
46 private void checkShortParse(final String msg, final Calendar serverTime, final Calendar input) throws ParseException {
47 checkShortParse(msg, serverTime, input, false);
48 checkShortParse(msg, serverTime, input, true);
49 }
50
51
52
53
54
55
56
57
58
59 private void checkShortParse(final String msg, final Calendar servertime, final Calendar input, final boolean lenient) throws ParseException {
60 checkShortParse(msg, servertime, input, input, lenient);
61 }
62
63
64
65
66 private void checkShortParse(final String msg, final Calendar serverTime, final Calendar input, final Calendar expected) throws ParseException {
67 checkShortParse(msg, serverTime, input, expected, false);
68 checkShortParse(msg, serverTime, input, expected, true);
69 }
70
71
72
73
74
75
76
77
78
79
80 private void checkShortParse(final String msg, final Calendar servertime, final Calendar input, final Calendar expected, final boolean lenient)
81 throws ParseException {
82 final FTPTimestampParserImpl parser = new FTPTimestampParserImpl();
83 parser.setLenientFutureDates(lenient);
84 final SimpleDateFormat shortFormat = parser.getRecentDateFormat();
85
86 final String shortDate = shortFormat.format(input.getTime());
87 final Calendar output = parser.parseTimestamp(shortDate, servertime);
88 final int outyear = output.get(Calendar.YEAR);
89 final int outdom = output.get(Calendar.DAY_OF_MONTH);
90 final int outmon = output.get(Calendar.MONTH);
91 final int inyear = expected.get(Calendar.YEAR);
92 final int indom = expected.get(Calendar.DAY_OF_MONTH);
93 final int inmon = expected.get(Calendar.MONTH);
94 if (indom != outdom || inmon != outmon || inyear != outyear) {
95 final Format longFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm");
96 fail("Test: '" + msg + "' Server=" + longFormat.format(servertime.getTime()) + ". Failed to parse " + shortDate
97 + (lenient ? " (lenient)" : " (non-lenient)") + " using " + shortFormat.toPattern() + ". Actual " + longFormat.format(output.getTime())
98 + ". Expected " + longFormat.format(expected.getTime()));
99 }
100 }
101
102
103 @Test
104 public void testFeb29IfLeapYear() throws Exception {
105 final GregorianCalendar now = new GregorianCalendar();
106 final int thisYear = now.get(Calendar.YEAR);
107 final GregorianCalendar target = new GregorianCalendar(thisYear, Calendar.FEBRUARY, 29);
108 if (now.isLeapYear(thisYear) && now.after(target) && now.before(new GregorianCalendar(thisYear, Calendar.AUGUST, 29))) {
109 checkShortParse("Feb 29th", now, target);
110 } else {
111 System.out.println("Skipping Feb 29 test (not leap year or before Feb 29)");
112 }
113 }
114
115
116 @Test
117 public void testFeb29LeapYear() throws Exception {
118 final int year = 2000;
119 final GregorianCalendar now = new GregorianCalendar(year, Calendar.APRIL, 1, 12, 0);
120 checkShortParse("Feb 29th 2000", now, new GregorianCalendar(year, Calendar.FEBRUARY, 29));
121 }
122
123 @Test
124 public void testFeb29LeapYear2() throws Exception {
125 final int year = 2000;
126 final GregorianCalendar now = new GregorianCalendar(year, Calendar.MARCH, 1, 12, 0);
127 checkShortParse("Feb 29th 2000", now, new GregorianCalendar(year, Calendar.FEBRUARY, 29));
128 }
129
130
131 @Test
132 public void testFeb29LeapYear3() throws Exception {
133 final int year = 2000;
134 final GregorianCalendar now = new GregorianCalendar(year, Calendar.FEBRUARY, 29, 12, 0);
135 checkShortParse("Feb 29th 2000", now, new GregorianCalendar(year, Calendar.FEBRUARY, 29));
136 }
137
138
139 @Test
140 public void testFeb29LeapYear4() throws Exception {
141 final int year = 2000;
142 final GregorianCalendar now = new GregorianCalendar(year, Calendar.FEBRUARY, 28, 12, 0);
143
144 checkShortParse("Feb 29th 2000", now, new GregorianCalendar(year, Calendar.FEBRUARY, 29), true);
145 }
146
147
148 @Test
149 public void testFeb29NonLeapYear() {
150 final GregorianCalendar server = new GregorianCalendar(1999, Calendar.APRIL, 1, 12, 0);
151
152 final GregorianCalendar input = new GregorianCalendar(2000, Calendar.FEBRUARY, 29);
153 final GregorianCalendar expected = new GregorianCalendar(1999, Calendar.FEBRUARY, 29);
154 assertThrows(ParseException.class, () -> checkShortParse("Feb 29th 1999", server, input, expected, true));
155 assertThrows(ParseException.class, () -> checkShortParse("Feb 29th 1999", server, input, expected, false));
156 }
157
158 @Test
159 public void testNET444() throws Exception {
160 final FTPTimestampParserImpl parser = new FTPTimestampParserImpl();
161 parser.setLenientFutureDates(true);
162 final SimpleDateFormat sdf = new SimpleDateFormat(parser.getRecentDateFormatString());
163 final GregorianCalendar now = new GregorianCalendar(2012, Calendar.FEBRUARY, 28, 12, 0);
164
165 final GregorianCalendar nowplus1 = new GregorianCalendar(2012, Calendar.FEBRUARY, 28, 13, 0);
166
167 final String future1 = sdf.format(nowplus1.getTime());
168 final Calendar parsed1 = parser.parseTimestamp(future1, now);
169 assertEquals(nowplus1.get(Calendar.YEAR), parsed1.get(Calendar.YEAR));
170
171 final GregorianCalendar nowplus25 = new GregorianCalendar(2012, Calendar.FEBRUARY, 29, 13, 0);
172
173 final String future25 = sdf.format(nowplus25.getTime());
174 final Calendar parsed25 = parser.parseTimestamp(future25, now);
175 assertEquals(nowplus25.get(Calendar.YEAR) - 1, parsed25.get(Calendar.YEAR));
176 }
177
178
179
180
181 @Test
182 @Disabled
183 public void testNET446() throws Exception {
184 final GregorianCalendar server = new GregorianCalendar(2001, Calendar.JANUARY, 1, 12, 0);
185
186 final GregorianCalendar input = new GregorianCalendar(2000, Calendar.FEBRUARY, 29);
187 final GregorianCalendar expected = new GregorianCalendar(2000, Calendar.FEBRUARY, 29);
188 checkShortParse("Feb 29th 2000", server, input, expected);
189 }
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204 @Test
205 @Disabled
206 public void testNet710() throws ParseException {
207 final Calendar serverTime = Calendar.getInstance(TimeZone.getTimeZone("EDT"), Locale.US);
208 serverTime.set(2022, Calendar.MARCH, 16, 14, 0);
209 new FTPTimestampParserImpl().parseTimestamp("Mar 13 02:33", serverTime);
210 }
211
212 @Test
213 public void testParseDec31Lenient() throws Exception {
214 final GregorianCalendar now = new GregorianCalendar(2007, Calendar.DECEMBER, 30, 12, 0);
215 checkShortParse("2007-12-30", now, now);
216 final GregorianCalendar target = (GregorianCalendar) now.clone();
217 target.add(Calendar.DAY_OF_YEAR, +1);
218 checkShortParse("2007-12-31", now, target, true);
219 }
220
221 @Test
222 public void testParseJan01() throws Exception {
223 final GregorianCalendar now = new GregorianCalendar(2007, Calendar.JANUARY, 1, 12, 0);
224 checkShortParse("2007-01-01", now, now);
225 final GregorianCalendar target = new GregorianCalendar(2006, Calendar.DECEMBER, 31, 12, 0);
226 checkShortParse("2006-12-31", now, target, true);
227 checkShortParse("2006-12-31", now, target, false);
228 }
229
230 @Test
231 public void testParseJan01Lenient() throws Exception {
232 final GregorianCalendar now = new GregorianCalendar(2007, Calendar.DECEMBER, 31, 12, 0);
233 checkShortParse("2007-12-31", now, now);
234 final GregorianCalendar target = (GregorianCalendar) now.clone();
235 target.add(Calendar.DAY_OF_YEAR, +1);
236 checkShortParse("2008-1-1", now, target, true);
237 }
238
239 @Test
240 public void testParser() throws ParseException {
241
242 final Locale locale = Locale.getDefault();
243 try {
244 Locale.setDefault(Locale.ENGLISH);
245 final FTPTimestampParserImpl parser = new FTPTimestampParserImpl();
246 parser.parseTimestamp("feb 22 2002");
247 assertThrows(ParseException.class, () -> parser.parseTimestamp("f\u00e9v 22 2002"));
248
249 final FTPClientConfig config = new FTPClientConfig();
250 config.setDefaultDateFormatStr("d MMM yyyy");
251 config.setRecentDateFormatStr("d MMM HH:mm");
252 config.setServerLanguageCode("fr");
253 parser.configure(config);
254 assertThrows(ParseException.class, () -> parser.parseTimestamp("d\u00e9c 22 2002"), "incorrect.field.order");
255 try {
256 parser.parseTimestamp("22 d\u00e9c 2002");
257 } catch (final ParseException e) {
258 fail("failed.to.parse.french");
259 }
260 assertThrows(ParseException.class, () -> parser.parseTimestamp("22 dec 2002"), "incorrect.language");
261 assertThrows(ParseException.class, () -> parser.parseTimestamp("29 f\u00e9v 2002"), "nonexistent.date");
262 assertThrows(ParseException.class, () -> parser.parseTimestamp("22 ao\u00fb 30:02"), "bad.hour");
263 assertThrows(ParseException.class, () -> parser.parseTimestamp("22 ao\u00fb 20:74"), "bad.minute");
264 try {
265 parser.parseTimestamp("28 ao\u00fb 20:02");
266 } catch (final ParseException e) {
267 fail("failed.to.parse.french.recent");
268 }
269 } finally {
270 Locale.setDefault(locale);
271 }
272 }
273
274 @Test
275 public void testParseShortFutureDates1() throws Exception {
276 final GregorianCalendar now = new GregorianCalendar(2001, Calendar.MAY, 30, 12, 0);
277 checkShortParse("2001-5-30", now, now);
278 final GregorianCalendar target = (GregorianCalendar) now.clone();
279 target.add(Calendar.DAY_OF_MONTH, 1);
280 checkShortParse("2001-5-30 +1 day", now, target, true);
281 final AssertionError error = assertThrows(AssertionError.class, () -> checkShortParse("2001-5-30 +1 day", now, target, false));
282 if (error.getMessage().startsWith("Expected AssertionFailedError")) {
283 throw error;
284 }
285 target.add(Calendar.WEEK_OF_YEAR, 1);
286
287
288
289
290
291 }
292
293 @Test
294 public void testParseShortFutureDates2() throws Exception {
295 final GregorianCalendar now = new GregorianCalendar(2004, Calendar.AUGUST, 1, 12, 0);
296 checkShortParse("2004-8-1", now, now);
297 final GregorianCalendar target = (GregorianCalendar) now.clone();
298 target.add(Calendar.DAY_OF_MONTH, 1);
299 checkShortParse("2004-8-1 +1 day", now, target, true);
300 final AssertionError error = assertThrows(AssertionError.class, () -> checkShortParse("2004-8-1 +1 day", now, target, false));
301 if (error.getMessage().startsWith("Expected AssertionFailedError")) {
302 throw error;
303 }
304
305
306
307
308
309
310 }
311
312 @Test
313 public void testParseShortPastDates1() throws Exception {
314 final GregorianCalendar now = new GregorianCalendar(2001, Calendar.MAY, 30, 12, 0);
315 checkShortParse("2001-5-30", now, now);
316 final GregorianCalendar target = (GregorianCalendar) now.clone();
317 target.add(Calendar.WEEK_OF_YEAR, -1);
318 checkShortParse("2001-5-30 -1 week", now, target);
319 target.add(Calendar.WEEK_OF_YEAR, -12);
320 checkShortParse("2001-5-30 -13 weeks", now, target);
321 target.add(Calendar.WEEK_OF_YEAR, -13);
322 checkShortParse("2001-5-30 -26 weeks", now, target);
323 }
324
325 @Test
326 public void testParseShortPastDates2() throws Exception {
327 final GregorianCalendar now = new GregorianCalendar(2004, Calendar.AUGUST, 1, 12, 0);
328 checkShortParse("2004-8-1", now, now);
329 final GregorianCalendar target = (GregorianCalendar) now.clone();
330 target.add(Calendar.WEEK_OF_YEAR, -1);
331 checkShortParse("2004-8-1 -1 week", now, target);
332 target.add(Calendar.WEEK_OF_YEAR, -12);
333 checkShortParse("2004-8-1 -13 weeks", now, target);
334 target.add(Calendar.WEEK_OF_YEAR, -13);
335 checkShortParse("2004-8-1 -26 weeks", now, target);
336 }
337
338 @Test
339 public void testParseTimestamp() {
340 final Calendar cal = Calendar.getInstance();
341 cal.add(Calendar.HOUR_OF_DAY, 1);
342 cal.set(Calendar.SECOND, 0);
343 cal.set(Calendar.MILLISECOND, 0);
344 final Date anHourFromNow = cal.getTime();
345 final FTPTimestampParserImpl parser = new FTPTimestampParserImpl();
346 final SimpleDateFormat sdf = new SimpleDateFormat(parser.getRecentDateFormatString());
347 final String fmtTime = sdf.format(anHourFromNow);
348 try {
349 final Calendar parsed = parser.parseTimestamp(fmtTime);
350
351
352 assertEquals(1, cal.get(Calendar.YEAR) - parsed.get(Calendar.YEAR), "test.roll.back.year");
353 } catch (final ParseException e) {
354 fail("Unable to parse");
355 }
356 }
357
358 @Test
359 public void testParseTimestampAcrossTimeZones() {
360 final Calendar cal = Calendar.getInstance();
361 cal.set(Calendar.SECOND, 0);
362 cal.set(Calendar.MILLISECOND, 0);
363
364 cal.add(Calendar.HOUR_OF_DAY, 1);
365 final Date anHourFromNow = cal.getTime();
366
367 cal.add(Calendar.HOUR_OF_DAY, 2);
368 final Date threeHoursFromNow = cal.getTime();
369 cal.add(Calendar.HOUR_OF_DAY, -2);
370
371 final FTPTimestampParserImpl parser = new FTPTimestampParserImpl();
372
373
374
375 final FTPClientConfig config = new FTPClientConfig(FTPClientConfig.SYST_UNIX);
376 config.setDefaultDateFormatStr(FTPTimestampParser.DEFAULT_SDF);
377 config.setRecentDateFormatStr(FTPTimestampParser.DEFAULT_RECENT_SDF);
378
379 config.setServerTimeZoneId("America/Chicago");
380 config.setLenientFutureDates(false);
381 parser.configure(config);
382
383 final SimpleDateFormat sdf = (SimpleDateFormat) parser.getRecentDateFormat().clone();
384
385
386 final TimeZone tzla = TimeZone.getTimeZone("America/Los_Angeles");
387 sdf.setTimeZone(tzla);
388
389
390 final String fmtTimePlusOneHour = sdf.format(anHourFromNow);
391 final String fmtTimePlusThreeHours = sdf.format(threeHoursFromNow);
392
393 try {
394 final Calendar parsed = parser.parseTimestamp(fmtTimePlusOneHour);
395
396
397 assertEquals(TWO_HOURS_OF_MILLISECONDS, cal.getTime().getTime() - parsed.getTime().getTime(), "no.rollback.because.of.time.zones");
398 } catch (final ParseException e) {
399 fail("Unable to parse " + fmtTimePlusOneHour);
400 }
401
402
403
404
405
406 try {
407 final Calendar parsed = parser.parseTimestamp(fmtTimePlusThreeHours);
408
409 assertEquals(1, cal.get(Calendar.YEAR) - parsed.get(Calendar.YEAR), "rollback.even.with.time.zones");
410 } catch (final ParseException e) {
411 fail("Unable to parse" + fmtTimePlusThreeHours);
412 }
413 }
414
415 @Test
416 public void testParseTimestampWithSlop() {
417 final Calendar cal = Calendar.getInstance();
418 cal.set(Calendar.SECOND, 0);
419 cal.set(Calendar.MILLISECOND, 0);
420
421 final Calendar caltemp = (Calendar) cal.clone();
422 caltemp.add(Calendar.HOUR_OF_DAY, 1);
423 final Date anHourFromNow = caltemp.getTime();
424 caltemp.add(Calendar.DAY_OF_MONTH, 1);
425 final Date anHourFromNowTomorrow = caltemp.getTime();
426
427 final FTPTimestampParserImpl parser = new FTPTimestampParserImpl();
428
429
430 parser.setLenientFutureDates(true);
431
432 final SimpleDateFormat sdf = new SimpleDateFormat(parser.getRecentDateFormatString());
433 try {
434 String fmtTime = sdf.format(anHourFromNow);
435 Calendar parsed = parser.parseTimestamp(fmtTime);
436
437
438
439 assertEquals(0, cal.get(Calendar.YEAR) - parsed.get(Calendar.YEAR), "test.slop.no.roll.back.year");
440
441
442
443 fmtTime = sdf.format(anHourFromNowTomorrow);
444 parsed = parser.parseTimestamp(fmtTime);
445 assertEquals(1, cal.get(Calendar.YEAR) - parsed.get(Calendar.YEAR), "test.slop.roll.back.year");
446
447 } catch (final ParseException e) {
448 fail("Unable to parse");
449 }
450 }
451
452 }