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 package org.apache.commons.net.ftp.parser;
018
019 import java.text.Format;
020 import java.text.ParseException;
021 import java.text.SimpleDateFormat;
022 import java.util.Calendar;
023 import java.util.Date;
024 import java.util.GregorianCalendar;
025 import java.util.TimeZone;
026
027 import org.apache.commons.net.ftp.FTPClientConfig;
028
029 import junit.framework.TestCase;
030 import junit.framework.TestSuite;
031
032 /**
033 * Test the FTPTimestampParser class.
034 *
035 * @author scohen
036 *
037 */
038 public class FTPTimestampParserImplTest extends TestCase {
039
040 private static final int TWO_HOURS_OF_MILLISECONDS = 2 * 60 * 60 * 1000;
041
042 public void testParseTimestamp() {
043 Calendar cal = Calendar.getInstance();
044 cal.add(Calendar.HOUR_OF_DAY, 1);
045 cal.set(Calendar.SECOND,0);
046 cal.set(Calendar.MILLISECOND,0);
047 Date anHourFromNow = cal.getTime();
048 FTPTimestampParserImpl parser = new FTPTimestampParserImpl();
049 SimpleDateFormat sdf =
050 new SimpleDateFormat(parser.getRecentDateFormatString());
051 String fmtTime = sdf.format(anHourFromNow);
052 try {
053 Calendar parsed = parser.parseTimestamp(fmtTime);
054 // since the timestamp is ahead of now (by one hour),
055 // this must mean the file's date refers to a year ago.
056 assertEquals("test.roll.back.year", 1, cal.get(Calendar.YEAR) - parsed.get(Calendar.YEAR));
057 } catch (ParseException e) {
058 fail("Unable to parse");
059 }
060 }
061
062 public void testParseTimestampWithSlop() {
063 Calendar cal = Calendar.getInstance();
064 cal.add(Calendar.HOUR_OF_DAY, 1);
065 cal.set(Calendar.SECOND,0);
066 cal.set(Calendar.MILLISECOND,0);
067 Date anHourFromNow = cal.getTime();
068 cal.add(Calendar.DATE, 1);
069 Date anHourFromNowTomorrow = cal.getTime();
070 cal.add(Calendar.DATE, -1);
071
072 FTPTimestampParserImpl parser = new FTPTimestampParserImpl();
073
074 // set the "slop" factor on
075 parser.setLenientFutureDates(true);
076
077 SimpleDateFormat sdf =
078 new SimpleDateFormat(parser.getRecentDateFormatString());
079 try {
080 String fmtTime = sdf.format(anHourFromNow);
081 Calendar parsed = parser.parseTimestamp(fmtTime);
082 // the timestamp is ahead of now (by one hour), but
083 // that's within range of the "slop" factor.
084 // so the date is still considered this year.
085 assertEquals("test.slop.no.roll.back.year", 0, cal.get(Calendar.YEAR) - parsed.get(Calendar.YEAR));
086
087 // add a day to get beyond the range of the slop factor.
088 // this must mean the file's date refers to a year ago.
089 fmtTime = sdf.format(anHourFromNowTomorrow);
090 parsed = parser.parseTimestamp(fmtTime);
091 assertEquals("test.slop.roll.back.year", 1, cal.get(Calendar.YEAR) - parsed.get(Calendar.YEAR));
092
093 } catch (ParseException e) {
094 fail("Unable to parse");
095 }
096 }
097
098 public void testParseTimestampAcrossTimeZones() {
099
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 }