/* * DateParser.java February 2001 * * Copyright (C) 2001, Niall Gallagher * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or * implied. See the License for the specific language governing * permissions and limitations under the License. */ package org.simpleframework.http.parse; import static java.util.Calendar.DAY_OF_MONTH; import static java.util.Calendar.DAY_OF_WEEK; import static java.util.Calendar.HOUR_OF_DAY; import static java.util.Calendar.MILLISECOND; import static java.util.Calendar.MINUTE; import static java.util.Calendar.MONTH; import static java.util.Calendar.SECOND; import static java.util.Calendar.YEAR; import java.util.Calendar; import java.util.TimeZone; import org.simpleframework.common.parse.Parser; /** * This is used to create a Parser for the HTTP date format. * This will parse the 3 formats that are acceptable for the HTTP/1.1 date. * The three formats that are acceptable for the HTTP-date are *
 * Sun, 06 Nov 1994 08:49:37 GMT  ; RFC 822, updated by RFC 1123
 * Sunday, 06-Nov-94 08:49:37 GMT ; RFC 850, obsoleted by RFC 1036
 * Sun Nov  6 08:49:37 1994       ; ANSI C's asctime() format
 * 
*

* This can also parse the date in ms as retrived from the System's * System.currentTimeMillis method. This has a parse method for a * long which will do the same as the parse(String). * Once the date has been parsed there are two methods that allow the date * to be represented, the toLong method converts the date to a * long and the toString method will convert the date * into a String. *

* This produces the same string as the SimpleDateFormat.format * using the pattern "EEE, dd MMM yyyy hh:mm:ss 'GMT'". This will * however do the job faster as it does not take arbitrary inputs. * * @author Niall Gallagher */ public class DateParser extends Parser { /** * Ensure that the time zone for dates if set to GMT. */ private static final TimeZone ZONE = TimeZone.getTimeZone("GMT"); /** * Contains the possible days of the week for RFC 1123. */ private static final String WKDAYS[] = { "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun" }; /** * Contains the possible days of the week for RFC 850. */ private static final String WEEKDAYS[] = { "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" }; /** * Contains the possible months in the year for HTTP-date. */ private static final String MONTHS[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; /** * Used as an index into the months array to get the month. */ private int month; /** * Represents the decimal value of the date such as 1977. */ private int year; /** * Represents the decimal value of the date such as 18. */ private int day; /** * Used as an index into the weekdays array to get the weekday. */ private int weekday; /** * Represents the decimal value of the hour such as 24. */ private int hour; /** * Represents the decimal value of the minute. */ private int mins; /** * Represents the decimal value for the second. */ private int secs; /** * The default constructor will create a parser that can parse * Strings that contain dates in the form of RFC 1123, * RFC 850 or asctime. If the dates that are to be parsed are not in * the form of one of these date encodings the results of this * parser will be random. */ public DateParser(){ this.init(); } /** * This constructor will conveniently parse the long argument * in the constructor. This can also be done by first calling the no-arg * constructor and then using the parse method. *

* This will then set this object to one that uses the RFC 1123 format * for a date. * * @param date the date to be parsed */ public DateParser(long date){ this(); parse(date); } /** This constructor will conveniently parse the String * argument in the constructor. This can also be done by first calling * the no-arg constructor and then using the parse method. *

* This will then set this object to one that uses the RFC 1123 format * for a date. * * @param date the date to be parsed */ public DateParser(String date) { this(); parse(date); } /** * This is used to extract the date from a long. If this * method is given the value of the date as a long it will * construct the RFC 1123 date as required by RFC 2616 sec 3.3. *

* This saves time on parsing a String that is encoded in * the HTTP-date format. The date given must be positive, if the date * given is not a positive 'long' then the results * of this method is random/unknown. * * @param date the date to be parsed */ public void parse(long date){ Calendar calendar = Calendar.getInstance(ZONE); calendar.setTimeInMillis(date); weekday = calendar.get(DAY_OF_WEEK); year = calendar.get(YEAR); month = calendar.get(MONTH); day = calendar.get(DAY_OF_MONTH); hour = calendar.get(HOUR_OF_DAY); mins = calendar.get(MINUTE); secs = calendar.get(SECOND); month = month > 11 ? 11: month; weekday = (weekday+5) % 7; } /** * Convenience method used to convert the specified HTTP date in to a * long representing the time. This is used when a single method is * required to convert a HTTP date format to a usable long value for * use in creating Date objects. * * @param date the date specified in on of the HTTP date formats * * @return the date value as a long value in milliseconds */ public long convert(String date) { parse(date); return toLong(); } /** * Convenience method used to convert the specified long date in to a * HTTP date format. This is used when a single method is required to * convert a long data value in milliseconds to a HTTP date value. * * @param date the date specified as a long of milliseconds * * @return the date represented in the HTTP date format RFC 1123 */ public String convert(long date) { parse(date); return toString(); } /** * This is used to reset the date and the buffer variables * for this DateParser. Every in is set to the * value of 0. */ protected void init() { month = year = day = weekday = hour = mins = secs = off = 0; } /** * This is used to parse the contents of the buf. This * checks the fourth char of the buffer to see what it contains. Invariably * a date format belonging to RFC 1123 will have a ',' character in position 4, * a date format belonging to asctime will have a ' ' character in position 4 * and if neither of these characters are found at position 4 then it is * assumed that the date is in the RFC 850 fromat, however it may not be. */ protected void parse(){ if(buf.length<4)return; if(buf[3]==','){ rfc1123(); }else if(buf[3]==' '){ asctime(); }else{ rfc850(); } } /** * This will parse a date that is in the form of an RFC 1123 date. This * date format is the date format that is to be used with all applications * that are HTTP/1.1 compliant. The RFC 1123 date format is *

    * rfc1123 = 'wkday "," SP date1 SP time SP GMT'. 
    * date1 = '2DIGIT SP month SP 4DIGIT' and finally
    * time = '2DIGIT ":" 2DIGIT ":" 2DIGIT'. 
    * 
*/ private void rfc1123(){ wkday(); off+=2; date1(); off++; time(); } /** * This will parse a date that is in the form of an RFC 850 date. This date * format is the date format that is to be used with some applications that * are HTTP/1.0 compliant. The RFC 1123 date format is *
    * rfc850 = 'weekday "," SP date2 SP time SP GMT'. 
    * date2 = '2DIGIT "-" month "-" 2DIGIT' and finally
    * time = '2DIGIT ":" 2DIGIT ":" 2DIGIT'. 
    * 
*/ private void rfc850() { weekday(); off+=2; date2(); off++; time(); } /** * This will parse a date that is in the form of an asctime date. This date * format is the date format that is to be used with some applications that * are HTTP/1.0 compliant. The RFC 1123 date format is *
    * asctime = 'weekday SP date3 SP time SP 4DIGIT'. 
    * date3 = 'month SP (2DIGIT | (SP 1DIGIT))' and 
    * time = '2DIGIT ":" 2DIGIT ":" 2DIGIT'. 
    * 
*/ private void asctime(){ wkday(); off++; date3(); off++; time(); off++; year4(); } /** * This is the date1 format of a date that is used by the RFC 1123 * date format. This date is *
    * date1 = '2DIGIT SP month SP 4DIGIT'.
    * example '02 Jun 1982'.
    * 
*/ private void date1(){ day(); off++; month(); off++; year4(); } /** * This is the date2 format of a date that is used by the RFC 850 * date format. This date is *
    * date2 = '2DIGIT "-" month "-" 2DIGIT'
    * example '02-Jun-82'.
    * 
*/ private void date2(){ day(); off++; month(); off++; year2(); } /** * This is the date3 format of a date that is used by the asctime * date format. This date is *
    * date3 = 'month SP (2DIGIT | (SP 1DIGIT))' 
    * example 'Jun  2'.
    * 
    */
   private void date3(){
      month();
      off++;
      day();
   }

   /** 
    * This is used to parse a consecutive set of digit characters to create 
    * the day of the week. This will tolerate a space on front of the digits 
    * thiswill allow all date formats including asctime to use this to get 
    * the day. This may parse more than 2 digits, however if there are more 
    * than 2 digits the date format is incorrect anyway.
    */
   private void day(){
      if(space(buf[off])){
         off++;
      }
      while(off < count){
         if(!digit(buf[off])){
            break;
         }
         day *= 10;
         day += buf[off];
         day -= '0';
         off++;
      }    
   }

   /** 
    * This is used to get the year from a set of digit characters. This is 
    * used to parse years that are of the form of 2 digits (e.g 82) however 
    * this will assume that any dates that are in 2 digit format are dates 
    * for the 2000 th milleneum so 01 will be 2001.
    * 

* This may parse more than 2 digits but if there are more than 2 digits * in a row then the date format is incorrect anyway. */ private void year2(){ int mill = 2000; /* milleneum */ int cent = 0; /* century */ while(off < count){ if(!digit(buf[off])){ break; } cent *= 10; cent += buf[off]; cent -= '0'; off++; } year= mill+cent; /* result 4 digits*/ } /** * This is used to get the year from a set of digit characters. This * is used to parse years that are of the form of 4 digits (e.g 1982). *

* This may parse more than 4 digits but if there are more than 2 * digits in a row then the date format is incorrect anyway. */ private void year4() { while(off < count){ if(!digit(buf[off])){ break; } year *= 10; year += buf[off]; year -= '0'; off++; } } /** * This is used to parse the time for a HTTP-date. The time for a * HTTP-date is in the form 00:00:00 that is *

    * time = '2DIGIT ":" 2DIGIT ":" 2DIGIT' so this will
    * read only a time of that form, although this will
    * parse time = '2DIGIT CHAR 2DIGIT CHAR 2DIGIT'.
    * 
*/ private void time(){ hours(); off++; mins(); off++; secs(); } /** * This is used to initialize the hour. This will read a consecutive * sequence of digit characters and convert them into a decimal number * to represent the hour that this date represents. *

* This may parse more than 2 digits but if there are more than 2 * digits the date is already incorrect. */ private void hours(){ while(off < count){ if(!digit(buf[off])){ break; } hour *= 10; hour += buf[off]; hour -= '0'; off++; } } /** * This is used to initialize the mins. This will read a consecutive * sequence of digit characters and convert them into a decimal number * to represent the mins that this date represents. *

* This may parse more than 2 digits but if there are more than 2 * digits the date is already incorrect. */ private void mins(){ while(off < count){ if(!digit(buf[off])){ break; } mins *= 10; mins += buf[off]; mins -= '0'; off++; } } /** * This is used to initialize the secs. This will read a consecutive * sequence of digit characters and convert them into a decimal * number to represent the secs that this date represents. *

* This may parse more than 2 digits but if there are more than 2 * digits the date is already incorrect */ private void secs(){ while(off < count){ if(!digit(buf[off])){ break; } secs *= 10; secs += buf[off]; secs -= '0'; off++; } } /** * This is used to read the week day of HTTP-date. The shorthand day * (e.g Mon for Monday) is used by the RFC 1123 and asctime date formats. * This will simply try to read each day from the buffer, when the day * is read successfully then the index of that day is saved. */ private void wkday(){ for(int i =0; i < WKDAYS.length;i++){ if(skip(WKDAYS[i])){ weekday = i; return; } } } /** * This is used to read the week day of HTTP-date. This format is used * by the RFC 850 date format. This will simply try to read each day from * the buffer, when the day is read successfully then the index of that * day is saved. */ private void weekday(){ for(int i =0; i < WKDAYS.length;i++){ if(skip(WEEKDAYS[i])){ weekday = i; return; } } } /** * This is used to read the month of HTTP-date. This will simply * try to read each month from the buffer, when the month is read * successfully then the index of that month is saved. */ private void month(){ for(int i =0; i < MONTHS.length;i++){ if(skip(MONTHS[i])){ month = i; return; } } } /** * This is used to append the date in RFC 1123 format to the given * string builder. This will append the date and a trailing space * character to the buffer. Dates like the following are appended. *

    * Tue, 02 Jun 1982 
    * 
. * For performance reasons a string builder is used. This avoids * an unneeded synchronization caused by the string buffers. * * @param builder this is the builder to append the date to */ private void date(StringBuilder builder) { builder.append(WKDAYS[weekday]); builder.append(", "); if(day <= 9) { builder.append('0'); } builder.append(day); builder.append(' '); builder.append(MONTHS[month]); builder.append(' '); builder.append(year); builder.append(' '); } /** * This is used to append the time in RFC 1123 format to the given * string builder. This will append the time and a trailing space * character to the buffer. Times like the following are appended. *
    * 23:59:59
    * 
. * For performance reasons a string builder is used. This avoids * an unneeded synchronization caused by the string buffers. * * @param builder this is the builder to write the time to */ private void time(StringBuilder builder) { if(hour <= 9) { builder.append('0'); } builder.append(hour); builder.append(':'); if(mins <= 9) { builder.append('0'); } builder.append(mins); builder.append(':'); if(secs <= 9) { builder.append('0'); } builder.append(secs); builder.append(' '); } /** * This is used to append the time zone to the provided appender. * For HTTP the dates should always be in GMT format. So this will * simply append the "GMT" string to the end of the builder. * * @param builder this builder to append the time zone to */ private void zone(StringBuilder builder) { builder.append("GMT"); } /** * This returns the date in as a long, given the exact * time this will use the java.util.Date to parse this date * into a long. The GregorianCalendar uses * the method getTime which produces the Date * object from this the getTime returns the long * * @return the date parsed as a long */ public long toLong() { Calendar calendar = Calendar.getInstance(ZONE); /* GMT*/ calendar.set(year,month, day, hour, mins, secs); calendar.set(MILLISECOND, 0); return calendar.getTime().getTime(); } /** * This prints the date in the format of a RFC 1123 date. Example *
    * Tue, 02 Jun 1982 23:59:59 GMT
    * 
. * This uses a StringBuffer to accumulate the various * Strings/ints to form the resulting date * value. The resulting date value is the one required by RFC 2616. *

* The HTTP date must be in the form of RFC 1123. The hours, minutes * and seconds are appended with the 0 character if they are less than * 9 i.e. if they do not have two digits. * * @return the date in RFC 1123 format */ public String toString(){ StringBuilder builder = new StringBuilder(30); date(builder); time(builder); zone(builder); return builder.toString(); } }