1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
47
48
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
59
60
61 private static final String[] W3CDATETIME_MASKS = {
62 "yyyy-MM-dd'T'HH:mm:ss.SSSz",
63 "yyyy-MM-dd't'HH:mm:ss.SSSz",
64 "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'",
65 "yyyy-MM-dd't'HH:mm:ss.SSS'z'",
66 "yyyy-MM-dd'T'HH:mm:ssz",
67 "yyyy-MM-dd't'HH:mm:ssz",
68 "yyyy-MM-dd'T'HH:mm:ss'Z'",
69 "yyyy-MM-dd't'HH:mm:ss'z'",
70 "yyyy-MM-dd'T'HH:mmz",
71 "yyyy-MM'T'HH:mmz",
72 "yyyy'T'HH:mmz",
73 "yyyy-MM-dd't'HH:mmz",
74 "yyyy-MM-dd'T'HH:mm'Z'",
75 "yyyy-MM-dd't'HH:mm'z'",
76 "yyyy-MM-dd",
77 "yyyy-MM",
78 "yyyy"
79 };
80
81
82
83 /***
84 * The masks used to validate and parse the input to this Atom date.
85 * These are a lot more forgiving than what the Atom spec allows.
86 * The forms that are invalid according to the spec are indicated.
87 */
88 private static final String[] masks = {
89 "yyyy-MM-dd'T'HH:mm:ss.SSSz",
90 "yyyy-MM-dd't'HH:mm:ss.SSSz",
91 "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'",
92 "yyyy-MM-dd't'HH:mm:ss.SSS'z'",
93 "yyyy-MM-dd'T'HH:mm:ssz",
94 "yyyy-MM-dd't'HH:mm:ssz",
95 "yyyy-MM-dd'T'HH:mm:ss'Z'",
96 "yyyy-MM-dd't'HH:mm:ss'z'",
97 "yyyy-MM-dd'T'HH:mmz",
98 "yyyy-MM-dd't'HH:mmz",
99 "yyyy-MM-dd'T'HH:mm'Z'",
100 "yyyy-MM-dd't'HH:mm'z'",
101 "yyyy-MM-dd",
102 "yyyy-MM",
103 "yyyy"
104 };
105
106
107
108
109 /***
110 * Private constructor to avoid DateParser instances creation.
111 */
112 private DateParser() {
113 }
114
115 /***
116 * Parses a Date out of a string using an array of masks.
117 * <p/>
118 * It uses the masks in order until one of them succedes or all fail.
119 * <p/>
120 *
121 * @param masks array of masks to use for parsing the string
122 * @param sDate string to parse for a date.
123 * @return the Date represented by the given string using one of the given masks.
124 * It returns <b>null</b> if it was not possible to parse the the string with any of the masks.
125 *
126 */
127 private static Date parseUsingMask(String[] masks,String sDate) {
128 sDate = (sDate!=null) ? sDate.trim() : null;
129 ParsePosition pp = null;
130 Date d = null;
131 for (int i=0;d==null && i<masks.length;i++) {
132 DateFormat df = new SimpleDateFormat(masks[i],Locale.US);
133
134 df.setLenient(true);
135 try {
136 pp = new ParsePosition(0);
137 d = df.parse(sDate,pp);
138 if (pp.getIndex()!=sDate.length()) {
139 d = null;
140 }
141
142 }
143 catch (Exception ex1) {
144
145 }
146 }
147 return d;
148 }
149
150 /***
151 * Parses a Date out of a String with a date in RFC822 format.
152 * <p/>
153 * It parsers the following formats:
154 * <ul>
155 * <li>"EEE, dd MMM yyyy HH:mm:ss z"</li>
156 * <li>"EEE, dd MMM yyyy HH:mm z"</li>
157 * <li>"EEE, dd MMM yy HH:mm:ss z"</li>
158 * <li>"EEE, dd MMM yy HH:mm z"</li>
159 * <li>"dd MMM yyyy HH:mm:ss z"</li>
160 * <li>"dd MMM yyyy HH:mm z"</li>
161 * <li>"dd MMM yy HH:mm:ss z"</li>
162 * <li>"dd MMM yy HH:mm z"</li>
163 * </ul>
164 * <p/>
165 * Refer to the java.text.SimpleDateFormat javadocs for details on the format of each element.
166 * <p/>
167 * @param sDate string to parse for a date.
168 * @return the Date represented by the given RFC822 string.
169 * It returns <b>null</b> if it was not possible to parse the given string into a Date.
170 *
171 */
172 public static Date parseRFC822(String sDate) {
173 int utIndex = sDate.indexOf(" UT");
174 if (utIndex>-1) {
175 String pre = sDate.substring(0,utIndex);
176 String post = sDate.substring(utIndex+3);
177 sDate = pre + " GMT" + post;
178 }
179 return parseUsingMask(RFC822_MASKS,sDate);
180 }
181
182
183 /***
184 * Parses a Date out of a String with a date in W3C date-time format.
185 * <p/>
186 * It parsers the following formats:
187 * <ul>
188 * <li>"yyyy-MM-dd'T'HH:mm:ssz"</li>
189 * <li>"yyyy-MM-dd'T'HH:mmz"</li>
190 * <li>"yyyy-MM-dd"</li>
191 * <li>"yyyy-MM"</li>
192 * <li>"yyyy"</li>
193 * </ul>
194 * <p/>
195 * Refer to the java.text.SimpleDateFormat javadocs for details on the format of each element.
196 * <p/>
197 * @param sDate string to parse for a date.
198 * @return the Date represented by the given W3C date-time string.
199 * It returns <b>null</b> if it was not possible to parse the given string into a Date.
200 *
201 */
202 public static Date parseW3CDateTime(String sDate) {
203
204
205 int tIndex = sDate.indexOf("T");
206 if (tIndex>-1) {
207 if (sDate.endsWith("Z")) {
208 sDate = sDate.substring(0,sDate.length()-1)+"+00:00";
209 }
210 int tzdIndex = sDate.indexOf("+",tIndex);
211 if (tzdIndex==-1) {
212 tzdIndex = sDate.indexOf("-",tIndex);
213 }
214 if (tzdIndex>-1) {
215 String pre = sDate.substring(0,tzdIndex);
216 int secFraction = pre.indexOf(",");
217 if (secFraction>-1) {
218 pre = pre.substring(0,secFraction);
219 }
220 String post = sDate.substring(tzdIndex);
221 sDate = pre + "GMT" + post;
222 }
223 }
224 else {
225 sDate += "T00:00GMT";
226 }
227 return parseUsingMask(W3CDATETIME_MASKS,sDate);
228 }
229
230
231 /***
232 * Parses a Date out of a String with a date in W3C date-time format or
233 * in a RFC822 format.
234 * <p>
235 * @param sDate string to parse for a date.
236 * @return the Date represented by the given W3C date-time string.
237 * It returns <b>null</b> if it was not possible to parse the given string into a Date.
238 *
239 * */
240 public static Date parseDate(String sDate) {
241 Date d = parseW3CDateTime(sDate);
242 if (d==null) {
243 d = parseRFC822(sDate);
244 if (d==null && ADDITIONAL_MASKS.length>0) {
245 d = parseUsingMask(ADDITIONAL_MASKS,sDate);
246 }
247 }
248 return d;
249 }
250
251 /***
252 * create a RFC822 representation of a date.
253 * <p/>
254 * Refer to the java.text.SimpleDateFormat javadocs for details on the format of each element.
255 * <p/>
256 * @param date Date to parse
257 * @return the RFC822 represented by the given Date
258 * It returns <b>null</b> if it was not possible to parse the date.
259 *
260 */
261 public static String formatRFC822(Date date) {
262 SimpleDateFormat dateFormater = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss 'GMT'",Locale.US);
263 dateFormater.setTimeZone(TimeZone.getTimeZone("GMT"));
264 return dateFormater.format(date);
265 }
266
267 /***
268 * create a W3C Date Time representation of a date.
269 * <p/>
270 * Refer to the java.text.SimpleDateFormat javadocs for details on the format of each element.
271 * <p/>
272 * @param date Date to parse
273 * @return the W3C Date Time represented by the given Date
274 * It returns <b>null</b> if it was not possible to parse the date.
275 *
276 */
277 public static String formatW3CDateTime(Date date) {
278 SimpleDateFormat dateFormater = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'",Locale.US);
279 dateFormater.setTimeZone(TimeZone.getTimeZone("GMT"));
280 return dateFormater.format(date);
281 }
282
283 }