geronimo-scm mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From rickmcgu...@apache.org
Subject svn commit: r628009 - in /geronimo/specs/trunk/geronimo-javamail_1.4_spec/src: main/java/javax/mail/internet/MailDateFormat.java test/java/javax/mail/internet/MailDateFormatTest.java
Date Fri, 15 Feb 2008 10:53:03 GMT
Author: rickmcguire
Date: Fri Feb 15 02:53:02 2008
New Revision: 628009

URL: http://svn.apache.org/viewvc?rev=628009&view=rev
Log:
GERONIMO-3853 Incompatibilities between Geronimo javamail MailDateFormat and Sun version.


Modified:
    geronimo/specs/trunk/geronimo-javamail_1.4_spec/src/main/java/javax/mail/internet/MailDateFormat.java
    geronimo/specs/trunk/geronimo-javamail_1.4_spec/src/test/java/javax/mail/internet/MailDateFormatTest.java

Modified: geronimo/specs/trunk/geronimo-javamail_1.4_spec/src/main/java/javax/mail/internet/MailDateFormat.java
URL: http://svn.apache.org/viewvc/geronimo/specs/trunk/geronimo-javamail_1.4_spec/src/main/java/javax/mail/internet/MailDateFormat.java?rev=628009&r1=628008&r2=628009&view=diff
==============================================================================
--- geronimo/specs/trunk/geronimo-javamail_1.4_spec/src/main/java/javax/mail/internet/MailDateFormat.java
(original)
+++ geronimo/specs/trunk/geronimo-javamail_1.4_spec/src/main/java/javax/mail/internet/MailDateFormat.java
Fri Feb 15 02:53:02 2008
@@ -21,11 +21,14 @@
 
 import java.text.FieldPosition;
 import java.text.NumberFormat;
+import java.text.ParseException;
 import java.text.ParsePosition;
 import java.text.SimpleDateFormat;
 import java.util.Calendar;
 import java.util.Date;
+import java.util.GregorianCalendar;
 import java.util.Locale;
+import java.util.TimeZone;
 
 /**
  * Formats ths date as specified by
@@ -40,15 +43,32 @@
  */
 public class MailDateFormat extends SimpleDateFormat {
     public MailDateFormat() {
-        super("EEE, d MMM yyyy HH:mm:ss Z", Locale.US);
+        super("EEE, d MMM yyyy HH:mm:ss Z (z)", Locale.US);
     }
 
     public StringBuffer format(Date date, StringBuffer buffer, FieldPosition position) {
         return super.format(date, buffer, position);
     }
 
+    /**
+     * Parse a Mail date into a Date object.  This uses fairly 
+     * lenient rules for the format because the Mail standards 
+     * for dates accept multiple formats.
+     * 
+     * @param string   The input string.
+     * @param position The position argument.
+     * 
+     * @return The Date object with the information inside. 
+     */
     public Date parse(String string, ParsePosition position) {
-        return super.parse(string, position);
+        MailDateParser parser = new MailDateParser(string, position);
+        try {
+            return parser.parse(isLenient()); 
+        } catch (ParseException e) {
+            e.printStackTrace(); 
+            // just return a null for any parsing errors 
+            return null; 
+        }
     }
 
     /**
@@ -67,5 +87,525 @@
      */
     public void setNumberFormat(NumberFormat format) {
         throw new UnsupportedOperationException();
+    }
+    
+    
+    // utility class for handling date parsing issues 
+    class MailDateParser {
+        // our list of defined whitespace characters 
+        static final String whitespace = " \t\r\n"; 
+        
+        // current parsing position 
+        int current; 
+        // our end parsing position 
+        int endOffset; 
+        // the date source string 
+        String source; 
+        // The parsing position. We update this as we move along and 
+        // also for any parsing errors 
+        ParsePosition pos; 
+        
+        public MailDateParser(String source, ParsePosition pos) 
+        {
+            this.source = source; 
+            this.pos = pos; 
+            // we start using the providing parsing index. 
+            this.current = pos.getIndex(); 
+            this.endOffset = source.length(); 
+        }
+        
+        /**
+         * Parse the timestamp, returning a date object. 
+         * 
+         * @param lenient The lenient setting from the Formatter object.
+         * 
+         * @return A Date object based off of parsing the date string.
+         * @exception ParseException
+         */
+        public Date parse(boolean lenient) throws ParseException {
+            // we just skip over any next date format, which means scanning ahead until we
+            // find the first numeric character 
+            locateNumeric(); 
+            // the day can be either 1 or two digits 
+            int day = parseNumber(1, 2); 
+            // step over the delimiter 
+            skipDateDelimiter(); 
+            // parse off the month (which is in character format) 
+            int month = parseMonth(); 
+            // step over the delimiter 
+            skipDateDelimiter(); 
+            // now pull of the year, which can be either 2-digit or 4-digit 
+            int year = parseYear(); 
+            // white space is required here 
+            skipRequiredWhiteSpace(); 
+            // accept a 1 or 2 digit hour 
+            int hour = parseNumber(1, 2);
+            skipRequiredChar(':'); 
+            // the minutes must be two digit 
+            int minutes = parseNumber(2, 2);
+            
+            // the seconds are optional, but the ":" tells us if they are to 
+            // be expected. 
+            int seconds = 0; 
+            if (skipOptionalChar(':')) {
+                seconds = parseNumber(2, 2); 
+            }
+            // skip over the white space 
+            skipWhiteSpace(); 
+            // and finally the timezone information 
+            int offset = parseTimeZone(); 
+            
+            // set the index of how far we've parsed this 
+            pos.setIndex(current);
+            
+            // create a calendar for creating the date 
+            Calendar greg = new GregorianCalendar(TimeZone.getTimeZone("GMT")); 
+            // we inherit the leniency rules 
+            greg.setLenient(lenient);
+            greg.set(year, month, day, hour, minutes, seconds); 
+            // now adjust by the offset.  This seems a little strange, but we  
+            // need to negate the offset because this is a UTC calendar, so we need to 
+            // apply the reverse adjustment.  for example, for the EST timezone, the offset

+            // value will be -300 (5 hours).  If the time was 15:00:00, the UTC adjusted
time 
+            // needs to be 20:00:00, so we subract -300 minutes. 
+            greg.add(Calendar.MINUTE, -offset); 
+            // now return this timestamp. 
+            return greg.getTime(); 
+        }
+        
+        
+        /**
+         * Skip over a position where there's a required value 
+         * expected. 
+         * 
+         * @param ch     The required character.
+         * 
+         * @exception ParseException
+         */
+        private void skipRequiredChar(char ch) throws ParseException {
+            if (current >= endOffset) {
+                parseError("Delimiter '" + ch + "' expected"); 
+            }
+            if (source.charAt(current) != ch) {
+                parseError("Delimiter '" + ch + "' expected"); 
+            }
+            current++; 
+        }
+        
+        
+        /**
+         * Skip over a position where iff the position matches the
+         * character
+         * 
+         * @param ch     The required character.
+         * 
+         * @return true if the character was there, false otherwise.
+         * @exception ParseException
+         */
+        private boolean skipOptionalChar(char ch) {
+            if (current >= endOffset) {
+                return false; 
+            }
+            if (source.charAt(current) != ch) {
+                return false; 
+            }
+            current++; 
+            return true; 
+        }
+        
+        
+        /**
+         * Skip over any white space characters until we find 
+         * the next real bit of information.  Will scan completely to the 
+         * end, if necessary. 
+         */
+        private void skipWhiteSpace() {
+            while (current < endOffset) {
+                // if this is not in the white space list, then success. 
+                if (whitespace.indexOf(source.charAt(current)) < 0) {
+                    return; 
+                }
+                current++; 
+            }
+            
+            // everything used up, just return 
+        }
+        
+        
+        /**
+         * Skip over any non-white space characters until we find 
+         * either a whitespace char or the end of the data.
+         */
+        private void skipNonWhiteSpace() {
+            while (current < endOffset) {
+                // if this is not in the white space list, then success. 
+                if (whitespace.indexOf(source.charAt(current)) >= 0) {
+                    return; 
+                }
+                current++; 
+            }
+            
+            // everything used up, just return 
+        }
+        
+        
+        /**
+         * Skip over any white space characters until we find 
+         * the next real bit of information.  Will scan completely to the 
+         * end, if necessary. 
+         */
+        private void skipRequiredWhiteSpace() throws ParseException {
+            int start = current; 
+            
+            while (current < endOffset) {
+                // if this is not in the white space list, then success. 
+                if (whitespace.indexOf(source.charAt(current)) < 0) {
+                    // we must have at least one white space character 
+                    if (start == current) {
+                        parseError("White space character expected"); 
+                    }
+                    return; 
+                }
+                current++; 
+            }
+            // everything used up, just return, but make sure we had at least one  
+            // white space
+            if (start == current) {
+                parseError("White space character expected"); 
+            }
+        }
+        
+        private void parseError(String message) throws ParseException {
+            // we've got an error, set the index to the end. 
+            pos.setErrorIndex(current);
+            throw new ParseException(message, current); 
+        }
+        
+        
+        /**
+         * Locate an expected numeric field. 
+         * 
+         * @exception ParseException
+         */
+        private void locateNumeric() throws ParseException {
+            while (current < endOffset) {
+                // found a digit?  we're done
+                if (Character.isDigit(source.charAt(current))) {
+                    return; 
+                }
+                current++; 
+            }
+            // we've got an error, set the index to the end. 
+            parseError("Number field expected"); 
+        }
+        
+        
+        /**
+         * Parse out an expected numeric field. 
+         * 
+         * @param minDigits The minimum number of digits we expect in this filed.
+         * @param maxDigits The maximum number of digits expected.  Parsing will
+         *                  stop at the first non-digit character.  An exception will
+         *                  be thrown if the field contained more than maxDigits
+         *                  in it.
+         * 
+         * @return The parsed numeric value. 
+         * @exception ParseException
+         */
+        private int parseNumber(int minDigits, int maxDigits) throws ParseException {
+            int start = current; 
+            int accumulator = 0; 
+            while (current < endOffset) {
+                char ch = source.charAt(current); 
+                // if this is not a digit character, then quit
+                if (!Character.isDigit(ch)) {
+                    break; 
+                }
+                // add the digit value into the accumulator 
+                accumulator = accumulator * 10 + Character.digit(ch, 10); 
+                current++; 
+            }
+            
+            int fieldLength = current - start; 
+            if (fieldLength < minDigits || fieldLength > maxDigits) {
+                parseError("Invalid number field"); 
+            }
+            
+            return accumulator; 
+        }
+        
+        /**
+         * Skip a delimiter between the date portions of the
+         * string.  The IMAP internal date format uses "-", so 
+         * we either accept a single "-" or any number of white
+         * space characters (at least one required). 
+         * 
+         * @exception ParseException
+         */
+        private void skipDateDelimiter() throws ParseException {
+            if (current >= endOffset) {
+                parseError("Invalid date field delimiter"); 
+            }
+            
+            if (source.charAt(current) == '-') {
+                current++; 
+            }
+            else {
+                // must be at least a single whitespace character 
+                skipRequiredWhiteSpace(); 
+            }
+        }
+        
+        
+        /**
+         * Parse a character month name into the date month 
+         * offset.
+         * 
+         * @return 
+         * @exception ParseException
+         */
+        private int parseMonth() throws ParseException {
+            if ((endOffset - current) < 3) {
+                parseError("Invalid month"); 
+            }
+            
+            int monthOffset = 0; 
+            String month = source.substring(current, current + 3).toLowerCase();
+            
+            if (month.equals("jan")) {
+                monthOffset = 0; 
+            }
+            else if (month.equals("feb")) {
+                monthOffset = 1; 
+            }
+            else if (month.equals("mar")) {
+                monthOffset = 2; 
+            }
+            else if (month.equals("apr")) {
+                monthOffset = 3; 
+            }
+            else if (month.equals("may")) {
+                monthOffset = 4; 
+            }
+            else if (month.equals("jun")) {
+                monthOffset = 5; 
+            }
+            else if (month.equals("jul")) {
+                monthOffset = 6; 
+            }
+            else if (month.equals("aug")) {
+                monthOffset = 7; 
+            }
+            else if (month.equals("sep")) {
+                monthOffset = 8; 
+            }
+            else if (month.equals("oct")) {
+                monthOffset = 9; 
+            }
+            else if (month.equals("nov")) {
+                monthOffset = 10; 
+            }
+            else if (month.equals("dec")) {
+                monthOffset = 11; 
+            }
+            else {
+                parseError("Invalid month"); 
+            }
+            
+            // ok, this is valid.  Update the position and return it 
+            current += 3;
+            return monthOffset; 
+        }
+        
+        /**
+         * Parse off a year field that might be expressed as 
+         * either 2 or 4 digits. 
+         * 
+         * @return The numeric value of the year. 
+         * @exception ParseException
+         */
+        private int parseYear() throws ParseException {
+            // the year is between 2 to 4 digits 
+            int year = parseNumber(2, 4); 
+            
+            // the two digit years get some sort of adjustment attempted. 
+            if (year < 50) {
+                year += 2000; 
+            }
+            else if (year < 100) {
+                year += 1990; 
+            }
+            return year; 
+        }
+        
+        
+        /**
+         * Parse all of the different timezone options. 
+         * 
+         * @return The timezone offset.
+         * @exception ParseException
+         */
+        private int parseTimeZone() throws ParseException {
+            if (current >= endOffset) {
+                parseError("Missing time zone"); 
+            }
+            
+            // get the first non-blank. If this is a sign character, this 
+            // is a zone offset.  
+            char sign = source.charAt(current); 
+            
+            if (sign == '-' || sign == '+') {
+                // need to step over the sign character 
+                current++; 
+                // a numeric timezone is always a 4 digit number, but 
+                // expressed as minutes/seconds.  I'm too lazy to write a 
+                // different parser that will bound on just a couple of characters, so 
+                // we'll grab this as a single value and adjust     
+                int zoneInfo = parseNumber(4, 4);
+                
+                int offset = (zoneInfo / 100) * 60 + (zoneInfo % 100); 
+                // negate this, if we have a negativeo offset 
+                if (sign == '-') {
+                    offset = -offset; 
+                }
+                return offset; 
+            }
+            else {
+                // need to parse this out using the obsolete zone names.  This will be 
+                // either a 3-character code (defined set), or a single character military

+                // zone designation. 
+                int start = current; 
+                skipNonWhiteSpace(); 
+                String name = source.substring(start, current).toUpperCase(); 
+                
+                if (name.length() == 1) {
+                    return militaryZoneOffset(name); 
+                }
+                else if (name.length() <= 3) {
+                    return namedZoneOffset(name); 
+                }
+                else {
+                    parseError("Invalid time zone"); 
+                }
+                return 0; 
+            }
+        }
+        
+        
+        /**
+         * Parse the obsolete mail timezone specifiers. The
+         * allowed set of timezones are terribly US centric. 
+         * That's the spec.  The preferred timezone form is 
+         * the +/-mmss form. 
+         * 
+         * @param name   The input name.
+         * 
+         * @return The standard timezone offset for the specifier.
+         * @exception ParseException
+         */
+        private int namedZoneOffset(String name) throws ParseException {
+            
+            // NOTE:  This is "UT", NOT "UTC"
+            if (name.equals("UT")) {
+                return 0; 
+            }
+            else if (name.equals("GMT")) {
+                return 0; 
+            }
+            else if (name.equals("EST")) {
+                return -300; 
+            }
+            else if (name.equals("EDT")) {
+                return -240; 
+            }
+            else if (name.equals("CST")) {
+                return -360; 
+            }
+            else if (name.equals("CDT")) {
+                return -300; 
+            }
+            else if (name.equals("MST")) {
+                return -420; 
+            }
+            else if (name.equals("MDT")) {
+                return -360; 
+            }
+            else if (name.equals("PST")) {
+                return -480; 
+            }
+            else if (name.equals("PDT")) {
+                return -420; 
+            }
+            else {
+                parseError("Invalid time zone"); 
+                return 0; 
+            }
+        }
+        
+        
+        /**
+         * Parse a single-character military timezone. 
+         * 
+         * @param name   The one-character name.
+         * 
+         * @return The offset corresponding to the military designation.
+         */
+        private int militaryZoneOffset(String name) throws ParseException {
+            switch (Character.toUpperCase(name.charAt(0))) {
+                case 'A':
+                    return 60; 
+                case 'B':
+                    return 120; 
+                case 'C':
+                    return 180;
+                case 'D':
+                    return 240;
+                case 'E':
+                    return 300;
+                case 'F':
+                    return 360;
+                case 'G':
+                    return 420;
+                case 'H':
+                    return 480;
+                case 'I':
+                    return 540;
+                case 'K':
+                    return 600;
+                case 'L':
+                    return 660;
+                case 'M':
+                    return 720;
+                case 'N':
+                    return -60;
+                case 'O':
+                    return -120;
+                case 'P':
+                    return -180;
+                case 'Q':
+                    return -240;
+                case 'R':
+                    return -300;
+                case 'S':
+                    return -360;
+                case 'T':
+                    return -420;
+                case 'U':
+                    return -480;
+                case 'V':
+                    return -540;
+                case 'W':
+                    return -600;
+                case 'X':
+                    return -660;
+                case 'Y':
+                    return -720;
+                case 'Z':
+                    return 0;    
+                default:
+                    parseError("Invalid time zone");
+                    return 0; 
+            }
+        }
     }
 }

Modified: geronimo/specs/trunk/geronimo-javamail_1.4_spec/src/test/java/javax/mail/internet/MailDateFormatTest.java
URL: http://svn.apache.org/viewvc/geronimo/specs/trunk/geronimo-javamail_1.4_spec/src/test/java/javax/mail/internet/MailDateFormatTest.java?rev=628009&r1=628008&r2=628009&view=diff
==============================================================================
--- geronimo/specs/trunk/geronimo-javamail_1.4_spec/src/test/java/javax/mail/internet/MailDateFormatTest.java
(original)
+++ geronimo/specs/trunk/geronimo-javamail_1.4_spec/src/test/java/javax/mail/internet/MailDateFormatTest.java
Fri Feb 15 02:53:02 2008
@@ -44,5 +44,53 @@
         assertEquals(13, cal.get(Calendar.HOUR_OF_DAY));
         assertEquals(43, cal.get(Calendar.MINUTE));
         assertEquals(38, cal.get(Calendar.SECOND));
+        
+        date = mdf.parse("Wed, 27-Aug-2003 13:43:38 +0100");
+        // don't we just love the Date class?
+        cal = Calendar.getInstance(new SimpleTimeZone(+1 * 60 * 60 * 1000, "BST"), Locale.getDefault());
+        cal.setTime(date);
+        assertEquals(2003, cal.get(Calendar.YEAR));
+        assertEquals(Calendar.AUGUST, cal.get(Calendar.MONTH));
+        assertEquals(27, cal.get(Calendar.DAY_OF_MONTH));
+        assertEquals(Calendar.WEDNESDAY, cal.get(Calendar.DAY_OF_WEEK));
+        assertEquals(13, cal.get(Calendar.HOUR_OF_DAY));
+        assertEquals(43, cal.get(Calendar.MINUTE));
+        assertEquals(38, cal.get(Calendar.SECOND));
+        
+        date = mdf.parse("27-Aug-2003 13:43:38 EST");
+        // don't we just love the Date class?
+        cal = Calendar.getInstance(new SimpleTimeZone(-5 * 60 * 60 * 1000, "EST"), Locale.getDefault());
+        cal.setTime(date);
+        assertEquals(2003, cal.get(Calendar.YEAR));
+        assertEquals(Calendar.AUGUST, cal.get(Calendar.MONTH));
+        assertEquals(27, cal.get(Calendar.DAY_OF_MONTH));
+        assertEquals(Calendar.WEDNESDAY, cal.get(Calendar.DAY_OF_WEEK));
+        assertEquals(13, cal.get(Calendar.HOUR_OF_DAY));
+        assertEquals(43, cal.get(Calendar.MINUTE));
+        assertEquals(38, cal.get(Calendar.SECOND));
+        
+        date = mdf.parse("27 Aug 2003 13:43 EST");
+        // don't we just love the Date class?
+        cal = Calendar.getInstance(new SimpleTimeZone(-5 * 60 * 60 * 1000, "EST"), Locale.getDefault());
+        cal.setTime(date);
+        assertEquals(2003, cal.get(Calendar.YEAR));
+        assertEquals(Calendar.AUGUST, cal.get(Calendar.MONTH));
+        assertEquals(27, cal.get(Calendar.DAY_OF_MONTH));
+        assertEquals(Calendar.WEDNESDAY, cal.get(Calendar.DAY_OF_WEEK));
+        assertEquals(13, cal.get(Calendar.HOUR_OF_DAY));
+        assertEquals(43, cal.get(Calendar.MINUTE));
+        assertEquals(00, cal.get(Calendar.SECOND));
+        
+        date = mdf.parse("27 Aug 03 13:43 EST");
+        // don't we just love the Date class?
+        cal = Calendar.getInstance(new SimpleTimeZone(-5 * 60 * 60 * 1000, "EST"), Locale.getDefault());
+        cal.setTime(date);
+        assertEquals(2003, cal.get(Calendar.YEAR));
+        assertEquals(Calendar.AUGUST, cal.get(Calendar.MONTH));
+        assertEquals(27, cal.get(Calendar.DAY_OF_MONTH));
+        assertEquals(Calendar.WEDNESDAY, cal.get(Calendar.DAY_OF_WEEK));
+        assertEquals(13, cal.get(Calendar.HOUR_OF_DAY));
+        assertEquals(43, cal.get(Calendar.MINUTE));
+        assertEquals(00, cal.get(Calendar.SECOND));
     }
 }



Mime
View raw message