View Javadoc

1   package com.bradmcevoy.http;
2   
3   import java.text.DateFormat;
4   import java.text.ParseException;
5   import java.text.SimpleDateFormat;
6   import java.util.Arrays;
7   import java.util.Calendar;
8   import java.util.Collection;
9   import java.util.Date;
10  import java.util.Iterator;
11  import java.util.Locale;
12  import java.util.TimeZone;
13  import org.slf4j.Logger;
14  import org.slf4j.LoggerFactory;
15  
16  /**
17   * A utility class for parsing and formatting HTTP dates as used in cookies and
18   * other headers.  This class handles dates as defined by RFC 2616 section
19   * 3.3.1 as well as some other common non-standard formats.
20   *
21   * @author Christopher Brown
22   * @author Michael Becke
23   */
24  public class DateUtils {
25  
26      private static final Logger log = LoggerFactory.getLogger( DateUtils.class );
27      // 2005-03-30T05:18:33Z
28      public static final String PATTERN_WEBDAV = "yyyy-MM-dd HH:mm:ss";
29      /**
30       *  Used for response headers, and for modified date in propfind
31       */
32      public static final String PATTERN_RESPONSE_HEADER = "E, dd MMM yyyy HH:mm:ss z";
33      //public static final String PATTERN_RESPONSE_HEADER = "E MMM yyyy H:m:s z";
34      private static final ThreadLocal<DateFormat> thHeaderDateFormat = new ThreadLocal<DateFormat>();
35      /**
36       * Date format pattern used to parse HTTP date headers in RFC 1123 format.
37       */
38      public static final String PATTERN_RFC1123 = "EEE, dd MMM yyyy HH:mm:ss zzz";
39      /**
40       * Date format pattern used to parse HTTP date headers in RFC 1123 format.
41       */
42      public static final String PATTERN_RFC1123_NOSECS = "EEE, dd MMM yyyy HH:mm zzz";
43      /**
44       * Date format pattern used to parse HTTP date headers in RFC 1036 format.
45       */
46      public static final String PATTERN_RFC1036 = "EEEE, dd-MMM-yy HH:mm:ss zzz";
47      /**
48       * Date format pattern used to parse HTTP date headers in ANSI C
49       * <code>asctime()</code> format.
50       */
51      public static final String PATTERN_ASCTIME = "EEE MMM d HH:mm:ss yyyy";
52      /**
53       * Another Date format pattern used to parse HTTP date headers in ANSI C
54       * <code>asctime()</code> format.
55       */
56      public static final String PATTERN_ASCTIME2 = "EEE MMM yyyy HH:mm:ss zzz";
57      private static final Collection<String> DEFAULT_PATTERNS = Arrays.asList(
58          new String[]{PATTERN_ASCTIME, PATTERN_ASCTIME2, PATTERN_RFC1036, PATTERN_RFC1123, PATTERN_RFC1123_NOSECS, PATTERN_WEBDAV} );
59      private static final Date DEFAULT_TWO_DIGIT_YEAR_START;
60  
61      static {
62          Calendar calendar = Calendar.getInstance();
63          calendar.set( 2000, Calendar.JANUARY, 1, 0, 0 );
64          DEFAULT_TWO_DIGIT_YEAR_START = calendar.getTime();
65      }
66      public static final TimeZone GMT = TimeZone.getTimeZone( "GMT" );
67  
68      /**
69       * Parse date in format: 2010-09-03T09:29:43Z
70       * @param s
71       * @return
72       */
73      public static Date parseWebDavDate(String s) throws DateParseException {
74          //2010-09-03T09:29:43Z
75          s = s.replace( 'Z', ' ');
76          s = s.replace( 'T', ' ');
77          s = s.trim();
78          return parseDate( s );
79      }
80  
81      /**
82       * Parses a date value.  The formats used for parsing the date value are retrieved from
83       * the default http params.
84       *
85       * @param dateValue the date value to parse
86       *
87       * @return the parsed date
88       *
89       * @throws DateParseException if the value could not be parsed using any of the
90       * supported date formats
91       */
92      public static Date parseDate( String dateValue ) throws DateParseException {
93          return parseDate( dateValue, null, null );
94      }
95  
96      /**
97       * Parses the date value using the given date formats.
98       *
99       * @param dateValue the date value to parse
100      * @param dateFormats the date formats to use
101      *
102      * @return the parsed date
103      *
104      * @throws DateParseException if none of the dataFormats could parse the dateValue
105      */
106     public static Date parseDate( String dateValue, Collection<String> dateFormats )
107         throws DateParseException {
108         return parseDate( dateValue, dateFormats, null );
109     }
110 
111     /**
112      * Parses the date value using the given date formats.
113      *
114      * @param dateValue the date value to parse
115      * @param dateFormats the date formats to use
116      * @param startDate During parsing, two digit years will be placed in the range
117      * <code>startDate</code> to <code>startDate + 100 years</code>. This value may
118      * be <code>null</code>. When <code>null</code> is given as a parameter, year
119      * <code>2000</code> will be used.
120      *
121      * @return the parsed date
122      *
123      * @throws DateParseException if none of the dataFormats could parse the dateValue
124      */
125     public static Date parseDate(
126         String dateValue,
127         Collection<String> dateFormats,
128         Date startDate ) throws DateParseException {
129 
130 
131         if( dateValue == null ) {
132             throw new IllegalArgumentException( "dateValue is null" );
133         }
134         if( dateFormats == null ) {
135             dateFormats = DEFAULT_PATTERNS;
136         }
137         if( startDate == null ) {
138             startDate = DEFAULT_TWO_DIGIT_YEAR_START;
139         }
140         // trim single quotes around date if present
141         // see issue #5279
142         if( dateValue.length() > 1
143             && dateValue.startsWith( "'" )
144             && dateValue.endsWith( "'" ) ) {
145             dateValue = dateValue.substring( 1, dateValue.length() - 1 );
146         }
147 
148         SimpleDateFormat dateParser = null;
149         Iterator<String> formatIter = dateFormats.iterator();
150 
151         while( formatIter.hasNext() ) {
152             String format = formatIter.next();
153             if( dateParser == null ) {
154                 dateParser = new SimpleDateFormat( format, Locale.US );
155                 dateParser.setTimeZone( TimeZone.getTimeZone( "GMT" ) );
156                 dateParser.set2DigitYearStart( startDate );
157             } else {
158                 dateParser.applyPattern( format );
159             }
160             try {
161                 Date dt = dateParser.parse( dateValue );
162                 return dt;
163             } catch( ParseException pe ) {
164                 // ignore this exception, we will try the next format
165             }
166         }
167 
168         // we were unable to parse the date
169         throw new DateParseException( "Unable to parse the date " + dateValue );
170     }
171 
172     public static String formatDate( Date date ) {
173         Calendar cal = Calendar.getInstance(GMT );
174         cal.setTime( date );
175         return formatDate( cal );
176     }
177 
178     /**
179      *
180      * @see #PATTERN_WEBDAV
181      */
182     public static String formatDate( Calendar cal ) {
183         // 2005-03-30T05:18:33Z
184         StringBuilder sb = new StringBuilder();
185         sb.append( cal.get( Calendar.YEAR ) + "" );
186         sb.append( '-' );
187         sb.append( pad2( cal.get( Calendar.MONTH ) + 1 ) );
188         sb.append( '-' );
189         sb.append( pad2( cal.get( Calendar.DAY_OF_MONTH ) ) );
190 //        sb.append('-');
191 //        sb.append( pad2(cal.get(Calendar.DAY_OF_MONTH)) );
192 //        sb.append('-');
193 //        sb.append( pad2(cal.get(Calendar.MONTH)) );
194         sb.append( 'T' );
195         sb.append( pad2( cal.get( Calendar.HOUR_OF_DAY ) ) );
196         sb.append( ':' );
197         sb.append( pad2( cal.get( Calendar.MINUTE ) ) );
198         sb.append( ':' );
199         sb.append( pad2( cal.get( Calendar.SECOND ) ) );
200         sb.append( 'Z' );
201         String s = sb.toString();
202 //        log.debug(date.toString() + " -> " + s);
203         return s;
204     }
205 
206     public static String formatForHeader( Date date ) {
207         DateFormat df = thHeaderDateFormat.get();
208         if( df == null ) {
209             df = new SimpleDateFormat( DateUtils.PATTERN_RESPONSE_HEADER );
210             df.setTimeZone( TimeZone.getTimeZone( "GMT" ) );
211             thHeaderDateFormat.set( df );
212         }
213         return df.format( date );
214     }
215 
216     public static String pad2( int i ) {
217         if( i < 10 ) {
218             return "0" + i;
219         } else {
220             return i + "";
221         }
222     }
223 
224     /**
225      * Formats the given date according to the specified pattern.  The pattern
226      * must conform to that used by the {@link SimpleDateFormat simple date
227      * format} class.
228      *
229      * @param date The date to format.
230      * @param pattern The pattern to use for formatting the date.
231      * @return A formatted date string.
232      *
233      * @throws IllegalArgumentException If the given date pattern is invalid.
234      *
235      * @see SimpleDateFormat
236      */
237     public static String formatDate( Date date, String pattern ) {
238         if( date == null ) throw new IllegalArgumentException( "date is null" );
239         if( pattern == null )
240             throw new IllegalArgumentException( "pattern is null" );
241 
242         SimpleDateFormat formatter = new SimpleDateFormat( pattern, Locale.US );
243         formatter.setTimeZone( GMT );
244         return formatter.format( date );
245     }
246 
247     /**
248      * Use the long date form required for MS windows explorer
249      *
250      * @param date
251      * @return
252      */
253     public static String formatForWebDavModifiedDate( Date date ) {
254         return formatDate( date, PATTERN_RFC1123 );
255     }
256 
257     /** This class should not be instantiated. */
258     private DateUtils() {
259     }
260 
261     public static class DateParseException extends Exception {
262 
263         static final long serialVersionUID = 4417696455000643370L;
264 
265         /**
266          *
267          */
268         public DateParseException() {
269             super();
270         }
271 
272         /**
273          * @param message the exception message
274          */
275         public DateParseException( String message ) {
276             super( message );
277         }
278     }
279 }
280