1 /* HTTPDateFormat.java --
2    Copyright (C) 2004 Free Software Foundation, Inc.
3 
4 This file is part of GNU Classpath.
5 
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
9 any later version.
10 
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 General Public License for more details.
15 
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING.  If not, write to the
18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 02110-1301 USA.
20 
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library.  Thus, the terms and
23 conditions of the GNU General Public License cover the whole
24 combination.
25 
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module.  An independent module is a module which is not derived from
33 or based on this library.  If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so.  If you do not wish to do so, delete this
36 exception statement from your version. */
37 
38 
39 package gnu.java.net.protocol.http;
40 
41 import java.text.DateFormat;
42 import java.text.DecimalFormat;
43 import java.text.FieldPosition;
44 import java.text.NumberFormat;
45 import java.text.ParsePosition;
46 import java.util.Calendar;
47 import java.util.Date;
48 import java.util.GregorianCalendar;
49 import java.util.TimeZone;
50 
51 /**
52  * HTTP date formatter and parser.
53  * Formats dates according to RFC 822 (updated by RFC 1123).
54  * Parses dates according to the above, <i>or</i> RFC 1036, <i>or</i> the
55  * ANSI C <code>asctime()</code> format.
56  *
57  * @author Chris Burdess (dog@gnu.org)
58  */
59 public class HTTPDateFormat
60   extends DateFormat
61 {
62 
63   static final String[] DAYS_OF_WEEK = {
64     null, "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
65   };
66 
67   static final String[] MONTHS = {
68     "Jan", "Feb", "Mar", "Apr", "May", "Jun",
69     "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
70   };
71 
HTTPDateFormat()72   public HTTPDateFormat()
73   {
74     calendar = new GregorianCalendar(TimeZone.getTimeZone ("GMT"));
75     numberFormat = new DecimalFormat();
76   }
77 
78   /**
79    * Appends the textual value for the specified field to the given string
80    * buffer. This method should be avoided, use <code>format(Date)</code>
81    * instead.
82    * @param date the Date object
83    * @param buf the buffer to append to
84    * @param field the current field position
85    * @return the modified buffer
86    */
format(Date date, StringBuffer buf, FieldPosition field)87   public StringBuffer format(Date date, StringBuffer buf,
88                              FieldPosition field)
89   {
90     calendar.clear();
91     calendar.setTime(date);
92     buf.setLength(0);
93 
94     // Day of week
95     buf.append(DAYS_OF_WEEK[calendar.get(Calendar.DAY_OF_WEEK)]);
96     buf.append(',');
97     buf.append(' ');
98 
99     // Day of month
100     int day = calendar.get(Calendar.DAY_OF_MONTH);
101     buf.append(Character.forDigit(day / 10, 10));
102     buf.append(Character.forDigit(day % 10, 10));
103     buf.append(' ');
104 
105     // Month
106     buf.append(MONTHS[calendar.get(Calendar.MONTH)]);
107     buf.append(' ');
108 
109     // Year
110     int year = calendar.get(Calendar.YEAR);
111     if (year < 1000)
112       {
113         buf.append('0');
114         if (year < 100)
115           {
116             buf.append('0');
117             if (year < 10)
118               {
119                 buf.append('0');
120               }
121           }
122       }
123     buf.append(Integer.toString(year));
124     buf.append(' ');
125 
126     // Hour
127     int hour = calendar.get(Calendar.HOUR_OF_DAY);
128     buf.append(Character.forDigit(hour / 10, 10));
129     buf.append(Character.forDigit(hour % 10, 10));
130     buf.append(':');
131 
132     // Minute
133     int minute = calendar.get(Calendar.MINUTE);
134     buf.append(Character.forDigit(minute / 10, 10));
135     buf.append(Character.forDigit(minute % 10, 10));
136     buf.append(':');
137 
138     // Second
139     int second = calendar.get(Calendar.SECOND);
140     buf.append(Character.forDigit(second / 10, 10));
141     buf.append(Character.forDigit(second % 10, 10));
142     buf.append(' ');
143 
144     // Timezone
145     // Get time offset in minutes
146     int zoneOffset =(calendar.get(Calendar.ZONE_OFFSET) +
147                      calendar.get(Calendar.DST_OFFSET)) / 60000;
148 
149     // Apply + or - appropriately
150     if (zoneOffset < 0)
151       {
152         zoneOffset = -zoneOffset;
153         buf.append('-');
154       }
155     else
156       {
157         buf.append('+');
158       }
159 
160     // Set the 2 2-char fields as specified above
161     int tzhours = zoneOffset / 60;
162     buf.append(Character.forDigit(tzhours / 10, 10));
163     buf.append(Character.forDigit(tzhours % 10, 10));
164     int tzminutes = zoneOffset % 60;
165     buf.append(Character.forDigit(tzminutes / 10, 10));
166     buf.append(Character.forDigit(tzminutes % 10, 10));
167 
168     field.setBeginIndex(0);
169     field.setEndIndex(buf.length());
170     return buf;
171   }
172 
173   /**
174    * Parses the given date in the current TimeZone.
175    * @param text the formatted date to be parsed
176    * @param pos the current parse position
177    */
parse(String text, ParsePosition pos)178   public Date parse(String text, ParsePosition pos)
179   {
180     int date, month, year, hour, minute, second;
181     String monthText;
182     int start = 0, end = -1;
183     int len = text.length();
184     calendar.clear();
185     pos.setIndex(start);
186     try
187       {
188         // Advance to date
189         if (Character.isLetter(text.charAt(start)))
190           {
191             start = skipNonWhitespace(text, start);
192           }
193         // Determine mode
194         switch(start)
195           {
196           case 3:
197             // asctime
198             start = skipWhitespace(text, start);
199             pos.setIndex(start);
200             end = skipNonWhitespace(text, start + 1);
201             monthText = text.substring(start, end);
202             month = -1;
203             for (int i = 0; i < 12; i++)
204               {
205                 if (MONTHS[i].equals(monthText))
206                   {
207                     month = i;
208                     break;
209                   }
210               }
211             if (month == -1)
212               {
213                 pos.setErrorIndex(end);
214                 return null;
215               }
216             // Advance to date
217             start = skipWhitespace(text, end + 1);
218             pos.setIndex(start);
219             end = skipNonWhitespace(text, start + 1);
220             date = Integer.parseInt(text.substring(start, end));
221             // Advance to hour
222             start = skipWhitespace(text, end + 1);
223             pos.setIndex(start);
224             end = skipTo(text, start + 1, ':');
225             hour = Integer.parseInt(text.substring(start, end));
226             // Advance to minute
227             start = end + 1;
228             pos.setIndex(start);
229             end = skipTo(text, start + 1, ':');
230             minute = Integer.parseInt(text.substring(start, end));
231             // Advance to second
232             start = end + 1;
233             pos.setIndex(start);
234             end = skipNonWhitespace(text, start + 1);
235             second = Integer.parseInt(text.substring(start, end));
236             // Advance to year
237             start = skipWhitespace(text, end + 1);
238             pos.setIndex(start);
239             end = skipNonWhitespace(text, start + 1);
240             year = Integer.parseInt(text.substring(start, end));
241             break;
242           case 0:
243           case 4:
244             // rfc822
245             start = skipWhitespace(text, start);
246             pos.setIndex(start);
247             end = skipNonWhitespace(text, start + 1);
248             date = Integer.parseInt(text.substring(start, end));
249             // Advance to month
250             start = skipWhitespace(text, end + 1);
251             pos.setIndex(start);
252             end = skipNonWhitespace(text, start + 1);
253             monthText = text.substring(start, end);
254             month = -1;
255             for (int i = 0; i < 12; i++)
256               {
257                 if (MONTHS[i].equals(monthText))
258                   {
259                     month = i;
260                     break;
261                   }
262               }
263             if (month == -1)
264               {
265                 pos.setErrorIndex(end);
266                 return null;
267               }
268             // Advance to year
269             start = skipWhitespace(text, end + 1);
270             pos.setIndex(start);
271             end = skipNonWhitespace(text, start + 1);
272             year = Integer.parseInt(text.substring(start, end));
273             // Advance to hour
274             start = skipWhitespace(text, end + 1);
275             pos.setIndex(start);
276             end = skipTo(text, start + 1, ':');
277             hour = Integer.parseInt(text.substring(start, end));
278             // Advance to minute
279             start = end + 1;
280             pos.setIndex(start);
281             end = skipTo(text, start + 1, ':');
282             minute = Integer.parseInt(text.substring(start, end));
283             // Advance to second
284             start = end + 1;
285             pos.setIndex(start);
286             end = start + 1;
287             while (end < len && !Character.isWhitespace(text.charAt(end)))
288               {
289                 end++;
290               }
291             second = Integer.parseInt(text.substring(start, end));
292             break;
293           default:
294             // rfc850(obsolete)
295             start = skipWhitespace(text, start);
296             pos.setIndex(start);
297             end = skipTo(text, start + 1, '-');
298             date = Integer.parseInt(text.substring(start, end));
299             // Advance to month
300             start = end + 1;
301             pos.setIndex(start);
302             end = skipTo(text, start + 1, '-');
303             monthText = text.substring(start, end);
304             month = -1;
305             for (int i = 0; i < 12; i++)
306               {
307                 if (MONTHS[i].equals(monthText))
308                   {
309                     month = i;
310                     break;
311                   }
312               }
313             if (month == -1)
314               {
315                 pos.setErrorIndex(end);
316                 return null;
317               }
318             // Advance to year
319             start = end + 1;
320             pos.setIndex(start);
321             end = skipNonWhitespace(text, start + 1);
322             year = 1900 + Integer.parseInt(text.substring(start, end));
323             // Advance to hour
324             start = skipWhitespace(text, end + 1);
325             pos.setIndex(start);
326             end = skipTo(text, start + 1, ':');
327             hour = Integer.parseInt(text.substring(start, end));
328             // Advance to minute
329             start = end + 1;
330             pos.setIndex(start);
331             end = skipTo(text, start + 1, ':');
332             minute = Integer.parseInt(text.substring(start, end));
333             // Advance to second
334             start = end + 1;
335             pos.setIndex(start);
336             end = start + 1;
337             while (end < len && !Character.isWhitespace(text.charAt(end)))
338               {
339                 end++;
340               }
341             second = Integer.parseInt(text.substring(start, end));
342           }
343 
344         calendar.set(Calendar.YEAR, year);
345         calendar.set(Calendar.MONTH, month);
346         calendar.set(Calendar.DAY_OF_MONTH, date);
347         calendar.set(Calendar.HOUR, hour);
348         calendar.set(Calendar.MINUTE, minute);
349         calendar.set(Calendar.SECOND, second);
350 
351         if (end != len)
352           {
353             // Timezone
354             start = skipWhitespace(text, end + 1);
355             end = start + 1;
356             while (end < len && !Character.isWhitespace(text.charAt(end)))
357               {
358                 end++;
359               }
360             char pm = text.charAt(start);
361             if (Character.isLetter(pm))
362               {
363                 TimeZone tz =
364                   TimeZone.getTimeZone(text.substring(start, end));
365                 calendar.set(Calendar.ZONE_OFFSET, tz.getRawOffset());
366               }
367             else
368               {
369                 int zoneOffset = 0;
370                 zoneOffset += 600 * Character.digit(text.charAt(++start), 10);
371                 zoneOffset += 60 * Character.digit(text.charAt(++start), 10);
372                 zoneOffset += 10 * Character.digit(text.charAt(++start), 10);
373                 zoneOffset += Character.digit(text.charAt(++start), 10);
374                 zoneOffset *= 60000; // minutes -> ms
375                 if ('-' == pm)
376                   {
377                     zoneOffset = -zoneOffset;
378                   }
379                 calendar.set(Calendar.ZONE_OFFSET, zoneOffset);
380               }
381           }
382         pos.setIndex(end);
383 
384         return calendar.getTime();
385       }
386     catch (NumberFormatException e)
387       {
388         pos.setErrorIndex(Math.max(start, end));
389       }
390     catch (StringIndexOutOfBoundsException e)
391       {
392         pos.setErrorIndex(Math.max(start, end));
393       }
394     return null;
395   }
396 
skipWhitespace(String text, int pos)397   private int skipWhitespace(String text, int pos)
398   {
399     while(Character.isWhitespace(text.charAt(pos)))
400       {
401         pos++;
402       }
403     return pos;
404   }
405 
skipNonWhitespace(String text, int pos)406   private int skipNonWhitespace(String text, int pos)
407   {
408     while(!Character.isWhitespace(text.charAt(pos)))
409       {
410         pos++;
411       }
412     return pos;
413   }
414 
skipTo(String text, int pos, char c)415   private int skipTo(String text, int pos, char c)
416   {
417     while(text.charAt(pos) != c)
418       {
419         pos++;
420       }
421     return pos;
422   }
423 
424   /**
425    * Don't allow setting the calendar.
426    */
setCalendar(Calendar newCalendar)427   public void setCalendar(Calendar newCalendar)
428   {
429     throw new UnsupportedOperationException();
430   }
431 
432   /**
433    * Don't allow setting the NumberFormat.
434    */
setNumberFormat(NumberFormat newNumberFormat)435   public void setNumberFormat(NumberFormat newNumberFormat)
436   {
437     throw new UnsupportedOperationException();
438   }
439 
440 }
441