View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.net.ftp.parser;
18  
19  import java.text.Format;
20  import java.text.ParseException;
21  import java.text.SimpleDateFormat;
22  import java.util.Calendar;
23  import java.util.Date;
24  import java.util.GregorianCalendar;
25  import java.util.Locale;
26  import java.util.TimeZone;
27  
28  import org.apache.commons.net.ftp.FTPClientConfig;
29  
30  import junit.framework.AssertionFailedError;
31  import junit.framework.TestCase;
32  
33  /**
34   * Test the FTPTimestampParser class.
35   *
36   * @author scohen
37   *
38   */
39  public class FTPTimestampParserImplTest extends TestCase {
40  
41      private static final int TWO_HOURS_OF_MILLISECONDS = 2 * 60 * 60 * 1000;
42  
43      public void testParseTimestamp() {
44          Calendar cal = Calendar.getInstance();
45          cal.add(Calendar.HOUR_OF_DAY, 1);
46          cal.set(Calendar.SECOND,0);
47          cal.set(Calendar.MILLISECOND,0);
48          Date anHourFromNow = cal.getTime();
49          FTPTimestampParserImpl parser = new FTPTimestampParserImpl();
50          SimpleDateFormat sdf =
51              new SimpleDateFormat(parser.getRecentDateFormatString());
52          String fmtTime = sdf.format(anHourFromNow);
53          try {
54              Calendar parsed = parser.parseTimestamp(fmtTime);
55              // since the timestamp is ahead of now (by one hour),
56              // this must mean the file's date refers to a year ago.
57              assertEquals("test.roll.back.year", 1, cal.get(Calendar.YEAR) - parsed.get(Calendar.YEAR));
58          } catch (ParseException e) {
59              fail("Unable to parse");
60          }
61      }
62  
63      public void testParseTimestampWithSlop() {
64          Calendar cal = Calendar.getInstance();
65          cal.set(Calendar.SECOND,0);
66          cal.set(Calendar.MILLISECOND,0);
67  
68          Calendar caltemp = (Calendar) cal.clone();
69          caltemp.add(Calendar.HOUR_OF_DAY, 1);
70          Date anHourFromNow = caltemp.getTime();
71          caltemp.add(Calendar.DATE, 1);
72          Date anHourFromNowTomorrow = caltemp.getTime();
73  
74          FTPTimestampParserImpl parser = new FTPTimestampParserImpl();
75  
76          // set the "slop" factor on
77          parser.setLenientFutureDates(true);
78  
79          SimpleDateFormat sdf =
80              new SimpleDateFormat(parser.getRecentDateFormatString());
81          try {
82              String fmtTime = sdf.format(anHourFromNow);
83              Calendar parsed = parser.parseTimestamp(fmtTime);
84              // the timestamp is ahead of now (by one hour), but
85              // that's within range of the "slop" factor.
86              // so the date is still considered this year.
87              assertEquals("test.slop.no.roll.back.year", 0, cal.get(Calendar.YEAR) - parsed.get(Calendar.YEAR));
88  
89              // add a day to get beyond the range of the slop factor.
90              // this must mean the file's date refers to a year ago.
91              fmtTime = sdf.format(anHourFromNowTomorrow);
92              parsed = parser.parseTimestamp(fmtTime);
93              assertEquals("test.slop.roll.back.year", 1, cal.get(Calendar.YEAR) - parsed.get(Calendar.YEAR));
94  
95          } catch (ParseException e) {
96              fail("Unable to parse");
97          }
98      }
99  
100     public void testNET444() throws Exception {
101         FTPTimestampParserImpl parser = new FTPTimestampParserImpl();
102         parser.setLenientFutureDates(true);
103         SimpleDateFormat sdf = new SimpleDateFormat(parser.getRecentDateFormatString());
104         GregorianCalendar now = new GregorianCalendar(2012, Calendar.FEBRUARY, 28, 12, 0);
105 
106         GregorianCalendar nowplus1 = new GregorianCalendar(2012, Calendar.FEBRUARY, 28, 13, 0);
107         // Create a suitable short date
108         String future1 = sdf.format(nowplus1.getTime());
109         Calendar parsed1 = parser.parseTimestamp(future1, now);
110         assertEquals(nowplus1.get(Calendar.YEAR), parsed1.get(Calendar.YEAR));
111 
112         GregorianCalendar nowplus25 = new GregorianCalendar(2012, Calendar.FEBRUARY, 29, 13, 0);
113         // Create a suitable short date
114         String future25 = sdf.format(nowplus25.getTime());
115         Calendar parsed25 = parser.parseTimestamp(future25, now);
116         assertEquals(nowplus25.get(Calendar.YEAR) - 1, parsed25.get(Calendar.YEAR));
117     }
118 
119     public void testParseTimestampAcrossTimeZones() {
120 
121 
122         Calendar cal = Calendar.getInstance();
123         cal.set(Calendar.SECOND,0);
124         cal.set(Calendar.MILLISECOND,0);
125 
126         cal.add(Calendar.HOUR_OF_DAY, 1);
127         Date anHourFromNow = cal.getTime();
128 
129         cal.add(Calendar.HOUR_OF_DAY, 2);
130         Date threeHoursFromNow = cal.getTime();
131         cal.add(Calendar.HOUR_OF_DAY, -2);
132 
133         FTPTimestampParserImpl parser = new FTPTimestampParserImpl();
134 
135         // assume we are FTPing a server in Chicago, two hours ahead of
136         // L. A.
137         FTPClientConfig config =
138             new FTPClientConfig(FTPClientConfig.SYST_UNIX);
139         config.setDefaultDateFormatStr(FTPTimestampParser.DEFAULT_SDF);
140         config.setRecentDateFormatStr(FTPTimestampParser.DEFAULT_RECENT_SDF);
141         // 2 hours difference
142         config.setServerTimeZoneId("America/Chicago");
143         config.setLenientFutureDates(false); // NET-407
144         parser.configure(config);
145 
146         SimpleDateFormat sdf = (SimpleDateFormat)
147             parser.getRecentDateFormat().clone();
148 
149         // assume we're in the US Pacific Time Zone
150         TimeZone tzla = TimeZone.getTimeZone("America/Los_Angeles");
151         sdf.setTimeZone(tzla);
152 
153         // get formatted versions of time in L.A.
154         String fmtTimePlusOneHour = sdf.format(anHourFromNow);
155         String fmtTimePlusThreeHours = sdf.format(threeHoursFromNow);
156 
157 
158         try {
159             Calendar parsed = parser.parseTimestamp(fmtTimePlusOneHour);
160             // the only difference should be the two hours
161             // difference, no rolling back a year should occur.
162             assertEquals("no.rollback.because.of.time.zones",
163                 TWO_HOURS_OF_MILLISECONDS,
164                 cal.getTime().getTime() - parsed.getTime().getTime());
165         } catch (ParseException e){
166             fail("Unable to parse " + fmtTimePlusOneHour);
167         }
168 
169         //but if the file's timestamp is THREE hours ahead of now, that should
170         //cause a rollover even taking the time zone difference into account.
171         //Since that time is still later than ours, it is parsed as occurring
172         //on this date last year.
173         try {
174             Calendar parsed = parser.parseTimestamp(fmtTimePlusThreeHours);
175             // rollback should occur here.
176             assertEquals("rollback.even.with.time.zones",
177                     1, cal.get(Calendar.YEAR) - parsed.get(Calendar.YEAR));
178         } catch (ParseException e){
179             fail("Unable to parse" + fmtTimePlusThreeHours);
180         }
181     }
182 
183 
184     public void testParser() {
185         // This test requires an English Locale
186         Locale locale = Locale.getDefault();
187         try {
188             Locale.setDefault(Locale.ENGLISH);
189             FTPTimestampParserImpl parser = new FTPTimestampParserImpl();
190             try {
191                 parser.parseTimestamp("feb 22 2002");
192             } catch (ParseException e) {
193                 fail("failed.to.parse.default");
194             }
195             try {
196                 Calendar c = parser.parseTimestamp("f\u00e9v 22 2002");
197                 fail("should.have.failed.to.parse.default, but was: "+c.getTime().toString());
198             } catch (ParseException e) {
199                 // this is the success case
200             }
201 
202             FTPClientConfig config = new FTPClientConfig();
203             config.setDefaultDateFormatStr("d MMM yyyy");
204             config.setRecentDateFormatStr("d MMM HH:mm");
205             config.setServerLanguageCode("fr");
206             parser.configure(config);
207             try {
208                 parser.parseTimestamp("d\u00e9c 22 2002");
209                 fail("incorrect.field.order");
210             } catch (ParseException e) {
211                 // this is the success case
212             }
213             try {
214                 parser.parseTimestamp("22 d\u00e9c 2002");
215             } catch (ParseException e) {
216                 fail("failed.to.parse.french");
217             }
218 
219             try {
220                 parser.parseTimestamp("22 dec 2002");
221                 fail("incorrect.language");
222             } catch (ParseException e) {
223                 // this is the success case
224             }
225             try {
226                 parser.parseTimestamp("29 f\u00e9v 2002");
227                 fail("nonexistent.date");
228             } catch (ParseException e) {
229                 // this is the success case
230             }
231 
232             try {
233                 parser.parseTimestamp("22 ao\u00fb 30:02");
234                 fail("bad.hour");
235             } catch (ParseException e) {
236                 // this is the success case
237             }
238 
239             try {
240                 parser.parseTimestamp("22 ao\u00fb 20:74");
241                 fail("bad.minute");
242             } catch (ParseException e) {
243                 // this is the success case
244             }
245             try {
246                 parser.parseTimestamp("28 ao\u00fb 20:02");
247             } catch (ParseException e) {
248                 fail("failed.to.parse.french.recent");
249             }
250         } finally {
251             Locale.setDefault(locale);
252         }
253     }
254 
255     /*
256      * Check how short date is interpreted at a given time.
257      * Check both with and without lenient future dates
258      */
259     private void checkShortParse(String msg, Calendar serverTime, Calendar input) throws ParseException {
260         checkShortParse(msg, serverTime, input, false);
261         checkShortParse(msg, serverTime, input, true);
262     }
263 
264     /*
265      * Check how short date is interpreted at a given time.
266      * Check both with and without lenient future dates
267      */
268     private void checkShortParse(String msg, Calendar serverTime, Calendar input, Calendar expected) throws ParseException {
269         checkShortParse(msg, serverTime, input, expected, false);
270         checkShortParse(msg, serverTime, input, expected, true);
271     }
272 
273     /**
274      * Check how short date is interpreted at a given time
275      * Check only using specified lenient future dates setting
276      * @param msg identifying message
277      * @param servertime the time at the server
278      * @param input the time to be converted to a short date, parsed and tested against the full time
279      * @param lenient whether to use lenient mode or not.
280      */
281     private void checkShortParse(String msg, Calendar servertime, Calendar input, boolean lenient) throws ParseException {
282         checkShortParse(msg, servertime, input, input, lenient);
283     }
284 
285     /**
286      * Check how short date is interpreted at a given time
287      * Check only using specified lenient future dates setting
288      * @param msg identifying message
289      * @param servertime the time at the server
290      * @param input the time to be converted to a short date and parsed
291      * @param expected the expected result from parsing
292      * @param lenient whether to use lenient mode or not.
293      */
294     private void checkShortParse(String msg, Calendar servertime, Calendar input, Calendar expected, boolean lenient) throws ParseException {
295         FTPTimestampParserImpl parser = new FTPTimestampParserImpl();
296         parser.setLenientFutureDates(lenient);
297         Format shortFormat = parser.getRecentDateFormat(); // It's expecting this format
298 
299         final String shortDate = shortFormat.format(input.getTime());
300         Calendar output=parser.parseTimestamp(shortDate, servertime);
301         int outyear = output.get(Calendar.YEAR);
302         int outdom = output.get(Calendar.DAY_OF_MONTH);
303         int outmon = output.get(Calendar.MONTH);
304         int inyear = expected.get(Calendar.YEAR);
305         int indom = expected.get(Calendar.DAY_OF_MONTH);
306         int inmon = expected.get(Calendar.MONTH);
307         if (indom != outdom || inmon != outmon || inyear != outyear){
308             Format longFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm");
309             fail("Test: '"+msg+"' Server="+longFormat.format(servertime.getTime())
310                     +". Failed to parse "+shortDate
311                     +". Actual "+longFormat.format(output.getTime())
312                     +". Expected "+longFormat.format(expected.getTime()));
313         }
314     }
315 
316     public void testParseShortPastDates1() throws Exception {
317         GregorianCalendar now = new GregorianCalendar(2001, Calendar.MAY, 30, 12, 0);
318         checkShortParse("2001-5-30",now,now); // should always work
319         GregorianCalendar target = (GregorianCalendar) now.clone();
320         target.add(Calendar.WEEK_OF_YEAR, -1);
321         checkShortParse("2001-5-30 -1 week",now,target);
322         target.add(Calendar.WEEK_OF_YEAR, -12);
323         checkShortParse("2001-5-30 -13 weeks",now,target);
324         target.add(Calendar.WEEK_OF_YEAR, -13);
325         checkShortParse("2001-5-30 -26 weeks",now,target);
326     }
327 
328     public void testParseShortPastDates2() throws Exception {
329         GregorianCalendar now = new GregorianCalendar(2004, Calendar.AUGUST, 1, 12, 0);
330         checkShortParse("2004-8-1",now,now); // should always work
331         GregorianCalendar target = (GregorianCalendar) now.clone();
332         target.add(Calendar.WEEK_OF_YEAR, -1);
333         checkShortParse("2004-8-1 -1 week",now,target);
334         target.add(Calendar.WEEK_OF_YEAR, -12);
335         checkShortParse("2004-8-1 -13 weeks",now,target);
336         target.add(Calendar.WEEK_OF_YEAR, -13);
337         checkShortParse("2004-8-1 -26 weeks",now,target);
338     }
339 
340 //    Lenient mode allows for dates up to 1 day in the future
341 
342     public void testParseShortFutureDates1() throws Exception {
343         GregorianCalendar now = new GregorianCalendar(2001, Calendar.MAY, 30, 12, 0);
344         checkShortParse("2001-5-30",now,now); // should always work
345         GregorianCalendar target = (GregorianCalendar) now.clone();
346         target.add(Calendar.DAY_OF_MONTH, 1);
347         checkShortParse("2001-5-30 +1 day",now,target,true);
348         try {
349             checkShortParse("2001-5-30 +1 day",now,target,false);
350             fail("Expected AssertionFailedError");
351         } catch (AssertionFailedError pe) {
352             if (pe.getMessage().startsWith("Expected AssertionFailedError")) { // don't swallow our failure
353                 throw pe;
354             }
355         }
356         target.add(Calendar.WEEK_OF_YEAR, 1);
357 //        checkShortParse("2001-5-30 +1 week",now,target);
358 //        target.add(Calendar.WEEK_OF_YEAR, 12);
359 //        checkShortParse("2001-5-30 +13 weeks",now,target);
360 //        target.add(Calendar.WEEK_OF_YEAR, 13);
361 //        checkShortParse("2001-5-30 +26 weeks",now,target);
362     }
363 
364     public void testParseShortFutureDates2() throws Exception {
365         GregorianCalendar now = new GregorianCalendar(2004, Calendar.AUGUST, 1, 12, 0);
366         checkShortParse("2004-8-1",now,now); // should always work
367         GregorianCalendar target = (GregorianCalendar) now.clone();
368         target.add(Calendar.DAY_OF_MONTH, 1);
369         checkShortParse("2004-8-1 +1 day",now,target,true);
370         try {
371             checkShortParse("2004-8-1 +1 day",now,target,false);
372             fail("Expected AssertionFailedError");
373         } catch (AssertionFailedError pe) {
374             if (pe.getMessage().startsWith("Expected AssertionFailedError")) { // don't swallow our failure
375                 throw pe;
376             }
377         }
378 //        target.add(Calendar.WEEK_OF_YEAR, 1);
379 //        checkShortParse("2004-8-1 +1 week",now,target);
380 //        target.add(Calendar.WEEK_OF_YEAR, 12);
381 //        checkShortParse("2004-8-1 +13 weeks",now,target);
382 //        target.add(Calendar.WEEK_OF_YEAR, 13);
383 //        checkShortParse("2004-8-1 +26 weeks",now,target);
384     }
385 
386     // Test leap year if current year is a leap year
387     public void testFeb29IfLeapYear() throws Exception{
388         GregorianCalendar now = new GregorianCalendar();
389         final int thisYear = now.get(Calendar.YEAR);
390         if (now.isLeapYear(thisYear) && now.before(new GregorianCalendar(thisYear,Calendar.AUGUST,29))){
391             GregorianCalendar target = new GregorianCalendar(thisYear,Calendar.FEBRUARY,29);
392             checkShortParse("Feb 29th",now,target);
393         } else {
394             System.out.println("Skipping Feb 29 test");
395         }
396     }
397 
398     // Test Feb 29 for a known leap year
399     public void testFeb29LeapYear() throws Exception{
400         int year = 2000; // Use same year for current and short date
401         GregorianCalendar now = new GregorianCalendar(year, Calendar.APRIL, 1, 12, 0);
402         checkShortParse("Feb 29th 2000",now,new GregorianCalendar(year, Calendar.FEBRUARY,29));
403     }
404 
405     // Test Feb 29 for a known non-leap year - should fail
406     public void testFeb29NonLeapYear(){
407         GregorianCalendar server = new GregorianCalendar(1999, Calendar.APRIL, 1, 12, 0);
408         // Note: we use a known leap year for the target date to avoid rounding up
409         GregorianCalendar input = new GregorianCalendar(2000, Calendar.FEBRUARY,29);
410         GregorianCalendar expected = new GregorianCalendar(1999, Calendar.FEBRUARY,29);
411         try {
412             checkShortParse("Feb 29th 1999", server, input, expected, true);
413             fail("Should have failed to parse Feb 29th 1999");
414         } catch (ParseException pe) {
415         }
416         try {
417             checkShortParse("Feb 29th 1999", server, input, expected, false);
418             fail("Should have failed to parse Feb 29th 1999");
419         } catch (ParseException pe) {
420         }
421     }
422 
423     // This test currently fails, because we assume that short dates are +-6months when parsing Feb 29
424     public void DISABLEDtestNET446() throws Exception {
425         GregorianCalendar server = new GregorianCalendar(2001, Calendar.JANUARY, 1, 12, 0);
426         // Note: we use a known leap year for the target date to avoid rounding up
427         GregorianCalendar input = new GregorianCalendar(2000, Calendar.FEBRUARY,29);
428         GregorianCalendar expected = new GregorianCalendar(2000, Calendar.FEBRUARY,29);
429         checkShortParse("Feb 29th 2000", server, input, expected);
430     }
431 
432     public void testParseDec31Lenient() throws Exception {
433         GregorianCalendar now = new GregorianCalendar(2007, Calendar.DECEMBER, 30, 12, 0);
434         checkShortParse("2007-12-30",now,now); // should always work
435         GregorianCalendar target = (GregorianCalendar) now.clone();
436         target.add(Calendar.DAY_OF_YEAR, +1); // tomorrow
437         checkShortParse("2007-12-31",now,target, true);
438     }
439 
440     public void testParseJan01Lenient() throws Exception {
441         GregorianCalendar now = new GregorianCalendar(2007, Calendar.DECEMBER, 31, 12, 0);
442         checkShortParse("2007-12-31",now,now); // should always work
443         GregorianCalendar target = (GregorianCalendar) now.clone();
444         target.add(Calendar.DAY_OF_YEAR, +1); // tomorrow
445         checkShortParse("2008-1-1",now,target, true);
446     }
447 
448     public void testParseJan01() throws Exception {
449         GregorianCalendar now = new GregorianCalendar(2007, Calendar.JANUARY, 1, 12, 0);
450         checkShortParse("2007-01-01",now,now); // should always work
451         GregorianCalendar target = new GregorianCalendar(2006, Calendar.DECEMBER, 31, 12, 0);
452         checkShortParse("2006-12-31",now,target, true);
453         checkShortParse("2006-12-31",now,target, false);
454     }
455 
456 }