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.TimeZone;
26  
27  import org.apache.commons.net.ftp.FTPClientConfig;
28  
29  import junit.framework.TestCase;
30  import junit.framework.TestSuite;
31  
32  /**
33   * Test the FTPTimestampParser class.
34   * 
35   * @author scohen
36   *
37   */
38  public class FTPTimestampParserImplTest extends TestCase {
39      
40      private static final int TWO_HOURS_OF_MILLISECONDS = 2 * 60 * 60 * 1000;
41  
42      public void testParseTimestamp() {
43          Calendar cal = Calendar.getInstance();
44          cal.add(Calendar.HOUR_OF_DAY, 1);
45          cal.set(Calendar.SECOND,0);
46          cal.set(Calendar.MILLISECOND,0);
47          Date anHourFromNow = cal.getTime();
48          FTPTimestampParserImpl parser = new FTPTimestampParserImpl();
49          SimpleDateFormat sdf = 
50              new SimpleDateFormat(parser.getRecentDateFormatString());
51          String fmtTime = sdf.format(anHourFromNow);
52          try {
53              Calendar parsed = parser.parseTimestamp(fmtTime);
54              // since the timestamp is ahead of now (by one hour),
55              // this must mean the file's date refers to a year ago.
56              assertEquals("test.roll.back.year", 1, cal.get(Calendar.YEAR) - parsed.get(Calendar.YEAR));
57          } catch (ParseException e) {
58              fail("Unable to parse");
59          }
60      }
61          
62      public void testParseTimestampWithSlop() {
63          Calendar cal = Calendar.getInstance();
64          cal.add(Calendar.HOUR_OF_DAY, 1);
65          cal.set(Calendar.SECOND,0);
66          cal.set(Calendar.MILLISECOND,0);
67          Date anHourFromNow = cal.getTime();
68          cal.add(Calendar.DATE, 1);
69          Date anHourFromNowTomorrow = cal.getTime();
70          cal.add(Calendar.DATE, -1);
71  
72          FTPTimestampParserImpl parser = new FTPTimestampParserImpl();
73          
74          // set the "slop" factor on
75          parser.setLenientFutureDates(true);
76          
77          SimpleDateFormat sdf = 
78              new SimpleDateFormat(parser.getRecentDateFormatString());
79          try {
80              String fmtTime = sdf.format(anHourFromNow);
81              Calendar parsed = parser.parseTimestamp(fmtTime);
82              // the timestamp is ahead of now (by one hour), but
83              // that's within range of the "slop" factor.
84              // so the date is still considered this year.
85              assertEquals("test.slop.no.roll.back.year", 0, cal.get(Calendar.YEAR) - parsed.get(Calendar.YEAR));
86  
87              // add a day to get beyond the range of the slop factor. 
88              // this must mean the file's date refers to a year ago.
89              fmtTime = sdf.format(anHourFromNowTomorrow);
90              parsed = parser.parseTimestamp(fmtTime);
91              assertEquals("test.slop.roll.back.year", 1, cal.get(Calendar.YEAR) - parsed.get(Calendar.YEAR));
92              
93          } catch (ParseException e) {
94              fail("Unable to parse");
95          }
96      }
97  
98      public void testParseTimestampAcrossTimeZones() {
99          
100         
101         Calendar cal = Calendar.getInstance();
102         cal.set(Calendar.SECOND,0);
103         cal.set(Calendar.MILLISECOND,0);
104 
105         cal.add(Calendar.HOUR_OF_DAY, 1);
106         Date anHourFromNow = cal.getTime();
107         
108         cal.add(Calendar.HOUR_OF_DAY, 2);
109         Date threeHoursFromNow = cal.getTime();
110         cal.add(Calendar.HOUR_OF_DAY, -2);
111         
112         FTPTimestampParserImpl parser = new FTPTimestampParserImpl();
113 
114         // assume we are FTPing a server in Chicago, two hours ahead of 
115         // L. A.
116         FTPClientConfig config = 
117             new FTPClientConfig(FTPClientConfig.SYST_UNIX);
118         config.setDefaultDateFormatStr(FTPTimestampParser.DEFAULT_SDF);
119         config.setRecentDateFormatStr(FTPTimestampParser.DEFAULT_RECENT_SDF);
120         // 2 hours difference
121         config.setServerTimeZoneId("America/Chicago");
122         parser.configure(config);
123         
124         SimpleDateFormat sdf = (SimpleDateFormat)
125             parser.getRecentDateFormat().clone();
126         
127         // assume we're in the US Pacific Time Zone
128         TimeZone tzla = TimeZone.getTimeZone("America/Los_Angeles");
129         sdf.setTimeZone(tzla);
130         
131         // get formatted versions of time in L.A. 
132         String fmtTimePlusOneHour = sdf.format(anHourFromNow);
133         String fmtTimePlusThreeHours = sdf.format(threeHoursFromNow);
134         
135         
136         try {
137             Calendar parsed = parser.parseTimestamp(fmtTimePlusOneHour);
138             // the only difference should be the two hours
139             // difference, no rolling back a year should occur.
140             assertEquals("no.rollback.because.of.time.zones",
141                 TWO_HOURS_OF_MILLISECONDS, 
142                 cal.getTime().getTime() - parsed.getTime().getTime());
143         } catch (ParseException e){
144             fail("Unable to parse " + fmtTimePlusOneHour);
145         }
146         
147         //but if the file's timestamp is THREE hours ahead of now, that should 
148         //cause a rollover even taking the time zone difference into account.
149         //Since that time is still later than ours, it is parsed as occurring
150         //on this date last year.
151         try {
152             Calendar parsed = parser.parseTimestamp(fmtTimePlusThreeHours);
153             // rollback should occur here.
154             assertEquals("rollback.even.with.time.zones", 
155                     1, cal.get(Calendar.YEAR) - parsed.get(Calendar.YEAR));
156         } catch (ParseException e){
157             fail("Unable to parse" + fmtTimePlusThreeHours);
158         }
159     }
160 
161 
162     public void testParser() {
163         FTPTimestampParserImpl parser = new FTPTimestampParserImpl();
164         try {
165             parser.parseTimestamp("feb 22 2002");
166         } catch (ParseException e) {
167             fail("failed.to.parse.default");
168         }
169         try {
170             parser.parseTimestamp("f\u00e9v 22 2002");
171             fail("should.have.failed.to.parse.default");
172         } catch (ParseException e) {
173             // this is the success case
174         }
175 
176         FTPClientConfig config = new FTPClientConfig();
177         config.setDefaultDateFormatStr("d MMM yyyy");
178         config.setRecentDateFormatStr("d MMM HH:mm");
179         config.setServerLanguageCode("fr");
180         parser.configure(config);
181         try {
182             parser.parseTimestamp("d\u00e9c 22 2002");
183             fail("incorrect.field.order");
184         } catch (ParseException e) {
185             // this is the success case
186         }
187         try {
188             parser.parseTimestamp("22 d\u00e9c 2002");
189         } catch (ParseException e) {
190             fail("failed.to.parse.french");
191         }
192         
193         try {
194             parser.parseTimestamp("22 dec 2002");
195             fail("incorrect.language");
196         } catch (ParseException e) {
197             // this is the success case
198         }
199         try {
200             parser.parseTimestamp("29 f\u00e9v 2002");
201             fail("nonexistent.date");
202         } catch (ParseException e) {
203             // this is the success case
204         }
205 
206         try {
207             parser.parseTimestamp("22 ao\u00fb 30:02");
208             fail("bad.hour");
209         } catch (ParseException e) {
210             // this is the success case
211         }
212         
213         try {
214             parser.parseTimestamp("22 ao\u00fb 20:74");
215             fail("bad.minute");
216         } catch (ParseException e) {
217             // this is the success case
218         }
219         try {
220             parser.parseTimestamp("28 ao\u00fb 20:02");
221         } catch (ParseException e) {
222             fail("failed.to.parse.french.recent");
223         }
224     }
225     
226     /*
227      * Check how short date is interpreted at a given time.
228      * Check both with and without lenient future dates
229      */
230     private void checkShortParse(String msg, Calendar now, Calendar input) throws ParseException {
231         checkShortParse(msg, now, input, false);
232         checkShortParse(msg, now, input, true);
233     }
234 
235     /*
236      * Check how short date is interpreted at a given time
237      * Check only using specified lenient future dates setting
238      */
239     private void checkShortParse(String msg, Calendar now, Calendar input, boolean lenient) throws ParseException {
240         FTPTimestampParserImpl parser = new FTPTimestampParserImpl();
241         parser.setLenientFutureDates(lenient);
242         Format shortFormat = parser.getRecentDateFormat(); // It's expecting this format
243         Format longFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm");
244         
245         final String shortDate = shortFormat.format(input.getTime());
246         Calendar output=parser.parseTimestamp(shortDate, now);
247         int outyear = output.get(Calendar.YEAR);
248         int outdom = output.get(Calendar.DAY_OF_MONTH);
249         int outmon = output.get(Calendar.MONTH);
250         int inyear = input.get(Calendar.YEAR);
251         int indom = input.get(Calendar.DAY_OF_MONTH);
252         int inmon = input.get(Calendar.MONTH);
253         if (indom != outdom || inmon != outmon || inyear != outyear){
254             fail("Test: '"+msg+"' Server="+longFormat.format(now.getTime())
255                     +". Failed to parse "+shortDate
256                     +". Actual "+longFormat.format(output.getTime())
257                     +". Expected "+longFormat.format(input.getTime()));
258         }
259     }
260 
261     public void testParseShortPastDates1() throws Exception {
262         GregorianCalendar now = new GregorianCalendar(2001, Calendar.MAY, 30, 12, 0);
263         checkShortParse("2001-5-30",now,now); // should always work
264         GregorianCalendar target = (GregorianCalendar) now.clone();
265         target.add(Calendar.WEEK_OF_YEAR, -1);
266         checkShortParse("2001-5-30 -1 week",now,target);
267         target.add(Calendar.WEEK_OF_YEAR, -12);
268         checkShortParse("2001-5-30 -13 weeks",now,target);
269         target.add(Calendar.WEEK_OF_YEAR, -13);
270         checkShortParse("2001-5-30 -26 weeks",now,target);
271     }
272 
273     public void testParseShortPastDates2() throws Exception {
274         GregorianCalendar now = new GregorianCalendar(2004, Calendar.AUGUST, 1, 12, 0);
275         checkShortParse("2004-8-1",now,now); // should always work
276         GregorianCalendar target = (GregorianCalendar) now.clone();
277         target.add(Calendar.WEEK_OF_YEAR, -1);
278         checkShortParse("2004-8-1 -1 week",now,target);
279         target.add(Calendar.WEEK_OF_YEAR, -12);
280         checkShortParse("2004-8-1 -13 weeks",now,target);
281         target.add(Calendar.WEEK_OF_YEAR, -13);
282         checkShortParse("2004-8-1 -26 weeks",now,target);
283     }
284 
285 //    It has not yet been decided how to handle future dates, so skip these tests for now
286     
287 //    public void testParseShortFutureDates1() throws Exception {
288 //        GregorianCalendar now = new GregorianCalendar(2001, Calendar.MAY, 30, 12, 0);
289 //        checkShortParse("2001-5-30",now,now); // should always work
290 //        GregorianCalendar target = (GregorianCalendar) now.clone();
291 //        target.add(Calendar.WEEK_OF_YEAR, 1);
292 //        checkShortParse("2001-5-30 +1 week",now,target);
293 //        target.add(Calendar.WEEK_OF_YEAR, 12);
294 //        checkShortParse("2001-5-30 +13 weeks",now,target);
295 //        target.add(Calendar.WEEK_OF_YEAR, 13);
296 //        checkShortParse("2001-5-30 +26 weeks",now,target);
297 //    }
298 
299 //    public void testParseShortFutureDates2() throws Exception {
300 //        GregorianCalendar now = new GregorianCalendar(2004, Calendar.AUGUST, 1, 12, 0);
301 //        checkShortParse("2004-8-1",now,now); // should always work
302 //        GregorianCalendar target = (GregorianCalendar) now.clone();
303 //        target.add(Calendar.WEEK_OF_YEAR, 1);
304 //        checkShortParse("2004-8-1 +1 week",now,target);
305 //        target.add(Calendar.WEEK_OF_YEAR, 12);
306 //        checkShortParse("2004-8-1 +13 weeks",now,target);
307 //        target.add(Calendar.WEEK_OF_YEAR, 13);
308 //        checkShortParse("2004-8-1 +26 weeks",now,target);
309 //    }
310 
311     // Test leap year if current year is a leap year
312     public void testFeb29IfLeapYear() throws Exception{
313         GregorianCalendar now = new GregorianCalendar();
314         final int thisYear = now.get(Calendar.YEAR);
315         if (now.isLeapYear(thisYear) && now.before(new GregorianCalendar(thisYear,Calendar.AUGUST,29))){
316             GregorianCalendar target = new GregorianCalendar(thisYear,Calendar.FEBRUARY,29);            
317             checkShortParse("Feb 29th",now,target);
318         } else {
319             System.out.println("Skipping Feb 29 test");
320         }
321     }
322 
323     // Test Feb 29 for a known leap year
324     public void testFeb29LeapYear() throws Exception{
325         int year = 2000; // Use same year for current and short date
326         GregorianCalendar now = new GregorianCalendar(year, Calendar.APRIL, 1, 12, 0);
327         checkShortParse("Feb 29th 2000",now,new GregorianCalendar(year, Calendar.FEBRUARY,29));
328     }
329 
330     // Test Feb 29 for a known non-leap year - should fail
331     public void testFeb29NonLeapYear(){
332         GregorianCalendar now = new GregorianCalendar(1999, Calendar.APRIL, 1, 12, 0);
333         // Note: we use a known leap year for the target date to avoid rounding up
334         try {
335             checkShortParse("Feb 29th 1999",now,new GregorianCalendar(2000, Calendar.FEBRUARY,29));
336             fail("Should have failed to parse Feb 29th 1999");
337         } catch (ParseException expected) {
338         }
339     }
340 
341     public void testParseDec31Lenient() throws Exception {
342         GregorianCalendar now = new GregorianCalendar(2007, Calendar.DECEMBER, 30, 12, 0);
343         checkShortParse("2007-12-30",now,now); // should always work
344         GregorianCalendar target = (GregorianCalendar) now.clone();
345         target.add(Calendar.DAY_OF_YEAR, +1); // tomorrow
346         checkShortParse("2007-12-31",now,target, true);
347     }
348 
349     public void testParseJan01Lenient() throws Exception {
350         GregorianCalendar now = new GregorianCalendar(2007, Calendar.DECEMBER, 31, 12, 0);
351         checkShortParse("2007-12-31",now,now); // should always work
352         GregorianCalendar target = (GregorianCalendar) now.clone();
353         target.add(Calendar.DAY_OF_YEAR, +1); // tomorrow
354         checkShortParse("2008-1-1",now,target, true);
355     }
356 
357     public void testParseJan01() throws Exception {
358         GregorianCalendar now = new GregorianCalendar(2007, Calendar.JANUARY, 1, 12, 0);
359         checkShortParse("2007-01-01",now,now); // should always work
360         GregorianCalendar target = new GregorianCalendar(2006, Calendar.DECEMBER, 31, 12, 0);
361         checkShortParse("2006-12-31",now,target, true);
362         checkShortParse("2006-12-31",now,target, false);
363     }
364 
365     /**
366      * Method suite.
367      *
368      * @return TestSuite
369      */
370     public static TestSuite suite()
371     {
372         return(new TestSuite(FTPTimestampParserImplTest.class));
373     }
374 
375 
376 
377 }