View Javadoc

1   /*
2    * Copyright 2004 Sun Microsystems, Inc.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   *
16   */
17  package com.sun.syndication.io.impl;
18  
19  import java.text.DateFormat;
20  import java.text.SimpleDateFormat;
21  import java.text.ParsePosition;
22  import java.util.Date;
23  import java.util.TimeZone;
24  import java.util.Locale;
25  
26  /***
27   * A helper class that parses Dates out of Strings with date time in RFC822 and W3CDateTime
28   * formats plus the variants Atom (0.3) and RSS (0.9, 0.91, 0.92, 0.93, 0.94, 1.0 and 2.0)
29   * specificators added to those formats.
30   * <p/>
31   * It uses the JDK java.text.SimpleDateFormat class attemtping the parse using a mask for
32   * each one of the possible formats.
33   * <p/>
34   *
35   * @author Alejandro Abdelnur
36   *
37   */
38  public class DateParser {
39  
40      private static String[] ADDITIONAL_MASKS;
41  
42      static {
43          ADDITIONAL_MASKS = PropertiesLoader.getPropertiesLoader().getTokenizedProperty("datetime.extra.masks","|");
44      }
45  
46      // order is like this because the SimpleDateFormat.parse does not fail with exception
47      // if it can parse a valid date out of a substring of the full string given the mask
48      // so we have to check the most complete format first, then it fails with exception
49      private static final String[] RFC822_MASKS = {
50              "EEE, dd MMM yy HH:mm:ss z",
51              "EEE, dd MMM yy HH:mm z",
52              "dd MMM yy HH:mm:ss z",
53              "dd MMM yy HH:mm z"
54          };
55  
56  
57  
58      // order is like this because the SimpleDateFormat.parse does not fail with exception
59      // if it can parse a valid date out of a substring of the full string given the mask
60      // so we have to check the most complete format first, then it fails with exception
61      private static final String[] W3CDATETIME_MASKS = {
62          "yyyy-MM-dd'T'HH:mm:ssz",
63          "yyyy-MM-dd'T'HH:mmz",
64          "yyyy-MM-dd",
65          "yyyy-MM",
66          "yyyy"
67      };
68  
69      /***
70       * Private constructor to avoid DateParser instances creation.
71       */
72      private DateParser() {
73      }
74  
75      /***
76       * Parses a Date out of a string using an array of masks.
77       * <p/>
78       * It uses the masks in order until one of them succedes or all fail.
79       * <p/>
80       *
81       * @param masks array of masks to use for parsing the string
82       * @param sDate string to parse for a date.
83       * @return the Date represented by the given string using one of the given masks.
84       * It returns <b>null</b> if it was not possible to parse the the string with any of the masks.
85       *
86       */
87      private static Date parseUsingMask(String[] masks,String sDate) {
88          sDate = (sDate!=null) ? sDate.trim() : null;
89          ParsePosition pp = null;
90          Date d = null;
91          for (int i=0;d==null && i<masks.length;i++) {
92              DateFormat df = new SimpleDateFormat(masks[i],Locale.US);
93              //df.setLenient(false);
94              df.setLenient(true);
95              try {
96                  pp = new ParsePosition(0);
97                  d = df.parse(sDate,pp);
98                  if (pp.getIndex()!=sDate.length()) {
99                      d = null;
100                 }
101                 //System.out.println("pp["+pp.getIndex()+"] s["+sDate+" m["+masks[i]+"] d["+d+"]");
102             }
103             catch (Exception ex1) {
104                 //System.out.println("s: "+sDate+" m: "+masks[i]+" d: "+null);
105             }
106         }
107         return d;
108     }
109 
110     /***
111      * Parses a Date out of a String with a date in RFC822 format.
112      * <p/>
113      * It parsers the following formats:
114      * <ul>
115      *   <li>"EEE, dd MMM yyyy HH:mm:ss z"</li>
116      *   <li>"EEE, dd MMM yyyy HH:mm z"</li>
117      *   <li>"EEE, dd MMM yy HH:mm:ss z"</li>
118      *   <li>"EEE, dd MMM yy HH:mm z"</li>
119      *   <li>"dd MMM yyyy HH:mm:ss z"</li>
120      *   <li>"dd MMM yyyy HH:mm z"</li>
121      *   <li>"dd MMM yy HH:mm:ss z"</li>
122      *   <li>"dd MMM yy HH:mm z"</li>
123      * </ul>
124      * <p/>
125      * Refer to the java.text.SimpleDateFormat javadocs for details on the format of each element.
126      * <p/>
127      * @param sDate string to parse for a date.
128      * @return the Date represented by the given RFC822 string.
129      *         It returns <b>null</b> if it was not possible to parse the given string into a Date.
130      *
131      */
132     public static Date parseRFC822(String sDate) {
133         return parseUsingMask(RFC822_MASKS,sDate);
134     }
135 
136 
137     /***
138      * Parses a Date out of a String with a date in W3C date-time format.
139      * <p/>
140      * It parsers the following formats:
141      * <ul>
142      *   <li>"yyyy-MM-dd'T'HH:mm:ssz"</li>
143      *   <li>"yyyy-MM-dd'T'HH:mmz"</li>
144      *   <li>"yyyy-MM-dd"</li>
145      *   <li>"yyyy-MM"</li>
146      *   <li>"yyyy"</li>
147      * </ul>
148      * <p/>
149      * Refer to the java.text.SimpleDateFormat javadocs for details on the format of each element.
150      * <p/>
151      * @param sDate string to parse for a date.
152      * @return the Date represented by the given W3C date-time string.
153      *         It returns <b>null</b> if it was not possible to parse the given string into a Date.
154      *
155      */
156     public static Date parseW3CDateTime(String sDate) {
157         // if sDate has time on it, it injects 'GTM' before de TZ displacement to
158         // allow the SimpleDateFormat parser to parse it properly
159         int tIndex = sDate.indexOf("T");
160         if (tIndex>-1) {
161             if (sDate.endsWith("Z")) {
162                 sDate = sDate.substring(0,sDate.length()-1)+"+00:00";
163             }
164             int tzdIndex = sDate.indexOf("+",tIndex);
165             if (tzdIndex==-1) {
166                 tzdIndex = sDate.indexOf("-",tIndex);
167             }
168             if (tzdIndex>-1) {
169                 String pre = sDate.substring(0,tzdIndex);
170                 int secFraction = pre.indexOf(",");
171                 if (secFraction>-1) {
172                     pre = pre.substring(0,secFraction);
173                 }
174                 String post = sDate.substring(tzdIndex);
175                 sDate = pre + "GMT" + post;
176             }
177         }
178         return parseUsingMask(W3CDATETIME_MASKS,sDate);
179     }
180 
181 
182     /***
183      * Parses a Date out of a String with a date in W3C date-time format or
184      * in a RFC822 format.
185      * <p>
186      * @param sDate string to parse for a date.
187      * @return the Date represented by the given W3C date-time string.
188      *         It returns <b>null</b> if it was not possible to parse the given string into a Date.
189      *
190      * */
191     public static Date parseDate(String sDate) {
192         Date d = parseW3CDateTime(sDate);
193         if (d==null) {
194             d = parseRFC822(sDate);
195             if (d==null && ADDITIONAL_MASKS.length>0) {
196                 d = parseUsingMask(ADDITIONAL_MASKS,sDate);
197             }
198         }
199         return d;
200     }
201 
202     /***
203      * create a RFC822 representation of a date.
204      * <p/>
205      * Refer to the java.text.SimpleDateFormat javadocs for details on the format of each element.
206      * <p/>
207      * @param date Date to parse
208      * @return the RFC822 represented by the given Date
209      *         It returns <b>null</b> if it was not possible to parse the date.
210      *
211      */
212     public static String formatRFC822(Date date) {
213         SimpleDateFormat dateFormater = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss 'GMT'",Locale.US);
214         dateFormater.setTimeZone(TimeZone.getTimeZone("GMT"));
215         return dateFormater.format(date);
216     }
217 
218     /***
219      * create a W3C Date Time representation of a date.
220      * <p/>
221      * Refer to the java.text.SimpleDateFormat javadocs for details on the format of each element.
222      * <p/>
223      * @param date Date to parse
224      * @return the W3C Date Time represented by the given Date
225      *         It returns <b>null</b> if it was not possible to parse the date.
226      *
227      */
228     public static String formatW3CDateTime(Date date) {
229         SimpleDateFormat dateFormater = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'",Locale.US);
230         dateFormater.setTimeZone(TimeZone.getTimeZone("GMT"));
231         return dateFormater.format(date);
232     }
233 
234 }