All Downloads are FREE. Search and download functionalities are using the official Maven repository.

javax.mail.internet.MailDateFormat Maven / Gradle / Ivy

There is a newer version: 1.6.2
Show newest version
/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 1997-2013 Oracle and/or its affiliates. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License.  You can
 * obtain a copy of the License at
 * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
 * or packager/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at packager/legal/LICENSE.txt.
 *
 * GPL Classpath Exception:
 * Oracle designates this particular file as subject to the "Classpath"
 * exception as provided by Oracle in the GPL Version 2 section of the License
 * file that accompanied this code.
 *
 * Modifications:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [year] [name of copyright owner]"
 *
 * Contributor(s):
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */

package javax.mail.internet;

import java.util.Date;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.Locale;
import java.util.TimeZone;
import java.util.logging.Level;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.text.NumberFormat;
import java.text.FieldPosition;
import java.text.ParsePosition;
import java.text.ParseException;

import com.sun.mail.util.MailLogger;

/**
 * Formats and parses date specification based on
 * RFC 2822. 

* * This class does not take pattern strings. It always formats the * date based on the specification below.

* * 3.3. Date and Time Specification *

* Date and time occur in several header fields. This section specifies * the syntax for a full date and time specification. Though folding * white space is permitted throughout the date-time specification, it is * RECOMMENDED that a single space be used in each place that FWS appears * (whether it is required or optional); some older implementations may * not interpret other occurrences of folding white space correctly. *

 * date-time       =       [ day-of-week "," ] date FWS time [CFWS]
 *
 * day-of-week     =       ([FWS] day-name) / obs-day-of-week
 *
 * day-name        =       "Mon" / "Tue" / "Wed" / "Thu" /
 *                         "Fri" / "Sat" / "Sun"
 *
 * date            =       day month year
 *
 * year            =       4*DIGIT / obs-year
 *
 * month           =       (FWS month-name FWS) / obs-month
 *
 * month-name      =       "Jan" / "Feb" / "Mar" / "Apr" /
 *                         "May" / "Jun" / "Jul" / "Aug" /
 *                         "Sep" / "Oct" / "Nov" / "Dec"
 *
 * day             =       ([FWS] 1*2DIGIT) / obs-day
 *
 * time            =       time-of-day FWS zone
 *
 * time-of-day     =       hour ":" minute [ ":" second ]
 *
 * hour            =       2DIGIT / obs-hour
 *
 * minute          =       2DIGIT / obs-minute
 *
 * second          =       2DIGIT / obs-second
 *
 * zone            =       (( "+" / "-" ) 4DIGIT) / obs-zone
 * 
* The day is the numeric day of the month. The year is any numeric year * 1900 or later. *

* The time-of-day specifies the number of hours, minutes, and optionally * seconds since midnight of the date indicated. *

* The date and time-of-day SHOULD express local time. *

* The zone specifies the offset from Coordinated Universal Time (UTC, * formerly referred to as "Greenwich Mean Time") that the date and * time-of-day represent. The "+" or "-" indicates whether the * time-of-day is ahead of (i.e., east of) or behind (i.e., west of) * Universal Time. The first two digits indicate the number of hours * difference from Universal Time, and the last two digits indicate the * number of minutes difference from Universal Time. (Hence, +hhmm means * +(hh * 60 + mm) minutes, and -hhmm means -(hh * 60 + mm) minutes). The * form "+0000" SHOULD be used to indicate a time zone at Universal Time. * Though "-0000" also indicates Universal Time, it is used to indicate * that the time was generated on a system that may be in a local time * zone other than Universal Time and therefore indicates that the * date-time contains no information about the local time zone. *

* A date-time specification MUST be semantically valid. That is, the * day-of-the-week (if included) MUST be the day implied by the date, the * numeric day-of-month MUST be between 1 and the number of days allowed * for the specified month (in the specified year), the time-of-day MUST * be in the range 00:00:00 through 23:59:60 (the number of seconds * allowing for a leap second; see [STD12]), and the zone MUST be within * the range -9959 through +9959. * * @author Max Spivak * @since JavaMail 1.2 */ public class MailDateFormat extends SimpleDateFormat { private static final long serialVersionUID = -8148227605210628779L; public MailDateFormat() { super("EEE, d MMM yyyy HH:mm:ss 'XXXXX' (z)", Locale.US); } /** * Formats the given date in the format specified by * RFC 2822 in the current TimeZone. * * @param date the Date object * @param dateStrBuf the formatted string * @param fieldPosition the current field position * @return StringBuffer the formatted String * @since JavaMail 1.2 */ public StringBuffer format(Date date, StringBuffer dateStrBuf, FieldPosition fieldPosition) { /* How this method works: First format the date with the * format specified in the constructor inserting string 'XXXXX' * where the timezone offset goes. Find where in the string the * string 'XXXXX' appears and remember that in var "pos". * Calculate the offset, taking the DST into account and insert * it into the stringbuffer at position pos. */ int start = dateStrBuf.length(); super.format(date, dateStrBuf, fieldPosition); int pos = 0; // find the beginning of the 'XXXXX' string in the formatted date // 25 is the first position that we expect to find XXXXX at for (pos = start + 25; dateStrBuf.charAt(pos) != 'X'; pos++) ; // set the timezone to +HHMM or -HHMM calendar.clear(); calendar.setTime(date); int offset = calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET); // take care of the sign if (offset < 0) { dateStrBuf.setCharAt(pos++, '-'); offset = (-offset); } else dateStrBuf.setCharAt(pos++, '+'); int rawOffsetInMins = offset / 60 / 1000; // offset from GMT in mins int offsetInHrs = rawOffsetInMins / 60; int offsetInMins = rawOffsetInMins % 60; dateStrBuf.setCharAt(pos++, Character.forDigit((offsetInHrs/10), 10)); dateStrBuf.setCharAt(pos++, Character.forDigit((offsetInHrs%10), 10)); dateStrBuf.setCharAt(pos++, Character.forDigit((offsetInMins/10), 10)); dateStrBuf.setCharAt(pos++, Character.forDigit((offsetInMins%10), 10)); // done with timezone return dateStrBuf; } //////////////////////////////////////////////////////////// /** * Parses the given date in the format specified by * RFC 2822 in the current TimeZone. * * @param text the formatted date to be parsed * @param pos the current parse position * @return Date the parsed date in a Date object * @since JavaMail 1.2 */ public Date parse(String text, ParsePosition pos) { return parseDate(text.toCharArray(), pos, isLenient()); } /* Valid Examples: Date: Sun, 21 Mar 1993 23:56:48 -0800 (PST) Date: Date: Mon, 22 Mar 1993 09:41:09 -0800 (PST) Date: 26 Aug 76 14:29 EDT */ /** * method of what to look for: * * * skip WS * skip day "," (this is "Mon", "Tue") * skip WS * * parse number (until WS) ==> 1*2DIGIT (day of month) * * skip WS * * parse alpha chars (until WS) ==> find month * * skip WS * * parse number (until WS) ==> 2*4DIGIT (year) * * skip WS * * // now looking for time * parse number (until ':') ==> hours * parse number (until ':') ==> minutes * parse number (until WS) ==> seconds * * // now look for Time Zone * skip WS * if ('+' or '-') then numerical time zone offset * if (alpha) then alpha time zone offset */ static boolean debug = false; private static MailLogger logger = new MailLogger( MailDateFormat.class, "DEBUG", debug, System.out); /** * create a Date by parsing the char array */ static private Date parseDate(char[] orig, ParsePosition pos, boolean lenient) { try { int day = -1; int month = -1; int year = -1; int hours = 0; int minutes = 0; int seconds = 0; int offset = 0; MailDateParser p = new MailDateParser(orig, pos.getIndex()); // get the day p.skipUntilNumber(); day = p.parseNumber(); if (!p.skipIfChar('-')) { // for IMAP internal Date p.skipWhiteSpace(); } // get the month month = p.parseMonth(); if (!p.skipIfChar('-')) { // for IMAP internal Date p.skipWhiteSpace(); } // get the year year = p.parseNumber(); // should not return a negative number if (year < 50) { year += 2000; } else if (year < 100) { year += 1900; } // otherwise the year is correct (and should be 4 digits) // get the time // first get hours p.skipWhiteSpace(); hours = p.parseNumber(); // get minutes p.skipChar(':'); minutes = p.parseNumber(); // get seconds (may be no seconds) if (p.skipIfChar(':')) { seconds = p.parseNumber(); } // try to get a Time Zone try { p.skipWhiteSpace(); offset = p.parseTimeZone(); } catch (ParseException pe) { if (logger.isLoggable(Level.FINE)) { logger.log(Level.FINE, "No timezone? : '" + new String(orig) + "'", pe); } } pos.setIndex(p.getIndex()); Date d = ourUTC(year, month, day, hours, minutes, seconds, offset, lenient); return d; } catch (Exception e) { // Catch *all* exceptions, including RuntimeExceptions like // ArrayIndexOutofBoundsException ... we need to be // extra tolerant of all those bogus dates that might screw // up our parser. Sigh. if (logger.isLoggable(Level.FINE)) { logger.log(Level.FINE, "Bad date: '" + new String(orig) + "'", e); } pos.setIndex(1); // to prevent DateFormat.parse() from throwing ex return null; } } private static final Calendar cal = new GregorianCalendar(TimeZone.getTimeZone("GMT")); private synchronized static Date ourUTC(int year, int mon, int mday, int hour, int min, int sec, int tzoffset, boolean lenient) { // clear the time and then set all the values cal.clear(); cal.setLenient(lenient); cal.set(Calendar.YEAR, year); cal.set(Calendar.MONTH, mon); cal.set(Calendar.DATE, mday); cal.set(Calendar.HOUR_OF_DAY, hour); cal.set(Calendar.MINUTE, min); cal.add(Calendar.MINUTE, tzoffset); // adjust for the timezone cal.set(Calendar.SECOND, sec); return cal.getTime(); } //////////////////////////////////////////////////////////// /** Don't allow setting the calendar */ public void setCalendar(Calendar newCalendar) { throw new RuntimeException("Method setCalendar() shouldn't be called"); } /** Don't allow setting the NumberFormat */ public void setNumberFormat(NumberFormat newNumberFormat) { throw new RuntimeException("Method setNumberFormat() shouldn't be called"); } /* test code for MailDateFormat */ /* public static void main(String[] args) { DateFormat df = new MailDateFormat(); Date d = new Date(); // test output in all the timezones System.out.println("------- test all timezones ---------------"); System.out.println("Current date: " + d); String[] allIDs = TimeZone.getAvailableIDs(); for (int i = 0; i < allIDs.length; i++) { TimeZone tz = TimeZone.getTimeZone(allIDs[i]); df.setTimeZone(tz); System.out.println("Date in " + tz.getID() + ": " + df.format(new Date())); } try { System.out.println(df.parse("Sun, 21 Mar 1993 23:56:48 -0800 (PST)")); System.out.println(df.parse("Mon, 22 Mar 1994 13:34:51 +0000")); System.out.println(df.parse("26 Aug 76 14:29 EDT")); System.out.println(df.parse("15 Apr 11 23:49 EST")); System.out.println(df.parse("15 Apr 11 23:49 ABC")); } catch (ParseException pex) { pex.printStackTrace(); } // reset DateFormat TZ df.setTimeZone(TimeZone.getDefault()); // test all days in a month System.out.println(); System.out.println("------- test all days in a month ---------------"); Calendar cal = Calendar.getInstance(); cal.set(Calendar.YEAR, 1972); cal.set(Calendar.MONTH, Calendar.OCTOBER); cal.set(Calendar.DATE, 1); cal.set(Calendar.HOUR, 10); cal.set(Calendar.MINUTE, 50); cal.set(Calendar.AM_PM, Calendar.PM); System.out.println("Initial Date: " + cal.getTime()); System.out.println("Current Date: " + df.format(cal.getTime())); for (int i = 0; i < 30; i++) { cal.roll(Calendar.DATE, true); System.out.println("Current Date: " + df.format(cal.getTime())); } // test all months System.out.println(); System.out.println("------- test all months in a year -----------"); cal.set(Calendar.MONTH, Calendar.JANUARY); cal.set(Calendar.DATE, 7); System.out.println("Initial Date: " + cal.getTime()); System.out.println("Current Date: " + df.format(cal.getTime())); for (int i = 1; i < 12; i++) { cal.roll(Calendar.MONTH, true); System.out.println("Current Date: " + df.format(cal.getTime())); } // test leap years System.out.println(); System.out.println("------- test leap years -----------"); cal.set(Calendar.YEAR, 1999); cal.set(Calendar.MONTH, Calendar.JANUARY); cal.set(Calendar.DATE, 31); cal.roll(Calendar.MONTH, true); System.out.println("Initial Date: " + cal.getTime()); System.out.println("Current Date: " + df.format(cal.getTime())); for (int i = 1; i < 12; i++) { cal.set(Calendar.MONTH, Calendar.JANUARY); cal.set(Calendar.DATE, 31); cal.roll(Calendar.YEAR, true); cal.roll(Calendar.MONTH, true); System.out.println("Current Date: " + df.format(cal.getTime())); } } */ } /** * Helper class to deal with parsing the characters */ class MailDateParser { int index = 0; char[] orig = null; public MailDateParser(char[] orig, int index) { this.orig = orig; this.index = index; } /** * skips chars until it finds a number (0-9) * * if it does not find a number, it will throw * an ArrayIndexOutOfBoundsException */ public void skipUntilNumber() throws ParseException { try { while (true) { switch ( orig[index] ) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': return; default: index++; break; } } } catch (ArrayIndexOutOfBoundsException e) { throw new ParseException("No Number Found", index); } } /** * skips any number of tabs, spaces, CR, and LF - folding whitespace */ public void skipWhiteSpace() { int len = orig.length; while (index < len) { switch (orig[index]) { case ' ': // space case '\t': // tab case '\r': // CR case '\n': // LF index++; break; default: return; } } } /** * used to look at the next character without "parsing" that * character. */ public int peekChar() throws ParseException { if (index < orig.length) return orig[index]; else throw new ParseException("No more characters", index); } /** * skips the given character. if the current char does not * match a ParseException will be thrown */ public void skipChar(char c) throws ParseException { if (index < orig.length) { if (orig[index] == c) { index++; } else { throw new ParseException("Wrong char", index); } } else { throw new ParseException("No more characters", index); } } /** * will only skip the current char if it matches the given * char */ public boolean skipIfChar(char c) throws ParseException { if (index < orig.length) { if (orig[index] == c) { index++; return true; } else { return false; } } else { throw new ParseException("No more characters", index); } } /** * current char must point to a number. the number will be * parsed and the resulting number will be returned. if a * number is not found, a ParseException will be thrown */ public int parseNumber() throws ParseException { int length = orig.length; boolean gotNum = false; int result = 0; while (index < length) { switch( orig[index] ) { case '0': result *= 10; gotNum = true; break; case '1': result = result * 10 + 1; gotNum = true; break; case '2': result = result * 10 + 2; gotNum = true; break; case '3': result = result * 10 + 3; gotNum = true; break; case '4': result = result * 10 + 4; gotNum = true; break; case '5': result = result * 10 + 5; gotNum = true; break; case '6': result = result * 10 + 6; gotNum = true; break; case '7': result = result * 10 + 7; gotNum = true; break; case '8': result = result * 10 + 8; gotNum = true; break; case '9': result = result * 10 + 9; gotNum = true; break; default: if (gotNum) return result; else throw new ParseException("No Number found", index); } index++; } // check the result if (gotNum) return result; // else, throw a parse error throw new ParseException("No Number found", index); } /** * will look for one of "Jan/Feb/Mar/Apr/May/Jun/Jul/Aug/Sep/Oct/Nov/Dev" * and return the numerical version of the month. (0-11). a ParseException * error is thrown if a month cannot be found. */ public int parseMonth() throws ParseException { char curr; try { switch(orig[index++]) { case 'J': case 'j': // "Jan" (0) / "Jun" (5) / "Jul" (6) // check next char switch(orig[index++]) { case 'A': case 'a': curr = orig[index++]; if (curr == 'N' || curr == 'n') { return 0; } break; case 'U': case 'u': curr = orig[index++]; if (curr == 'N' || curr == 'n') { return 5; } else if (curr == 'L' || curr == 'l') { return 6; } break; } break; case 'F': case 'f': // "Feb" curr = orig[index++]; if (curr == 'E' || curr == 'e') { curr = orig[index++]; if (curr == 'B' || curr == 'b') { return 1; } } break; case 'M': case 'm': // "Mar" (2) / "May" (4) curr = orig[index++]; if (curr == 'A' || curr == 'a') { curr = orig[index++]; if (curr == 'R' || curr == 'r') { return 2; } else if (curr == 'Y' || curr == 'y') { return 4; } } break; case 'A': case 'a': // "Apr" (3) / "Aug" (7) curr = orig[index++]; if (curr == 'P' || curr == 'p') { curr = orig[index++]; if (curr == 'R' || curr == 'r') { return 3; } } else if (curr == 'U' || curr == 'u') { curr = orig[index++]; if (curr == 'G' || curr == 'g') { return 7; } } break; case 'S': case 's': // "Sep" (8) curr = orig[index++]; if (curr == 'E' || curr == 'e') { curr = orig[index++]; if (curr == 'P' || curr == 'p') { return 8; } } break; case 'O': case 'o': // "Oct" curr = orig[index++]; if (curr == 'C' || curr == 'c') { curr = orig[index++]; if (curr == 'T' || curr == 't') { return 9; } } break; case 'N': case 'n': // "Nov" curr = orig[index++]; if (curr == 'O' || curr == 'o') { curr = orig[index++]; if (curr == 'V' || curr == 'v') { return 10; } } break; case 'D': case 'd': // "Dec" curr = orig[index++]; if (curr == 'E' || curr == 'e') { curr = orig[index++]; if (curr == 'C' || curr == 'c') { return 11; } } break; } } catch (ArrayIndexOutOfBoundsException e) { } throw new ParseException("Bad Month", index); } /** * will parse the timezone - either Numerical version (e.g. +0800, -0500) * or the alpha version (e.g. PDT, PST). the result will be returned in * minutes needed to be added to the date to bring it to GMT. */ public int parseTimeZone() throws ParseException { if (index >= orig.length) throw new ParseException("No more characters", index); char test = orig[index]; if ( test == '+' || test == '-' ) { return parseNumericTimeZone(); } else { return parseAlphaTimeZone(); } } /** * will parse the Numerical time zone version (e.g. +0800, -0500) * the result will be returned in minutes needed to be added * to the date to bring it to GMT. */ public int parseNumericTimeZone() throws ParseException { // we switch the sign if it is a '+' // since the time in the string we are // parsing is off from GMT by that amount. // and we want to get the time back into // GMT, so we substract it. boolean switchSign = false; char first = orig[index++]; if (first == '+') { switchSign = true; } else if (first != '-') { throw new ParseException("Bad Numeric TimeZone", index); } int oindex = index; int tz = parseNumber(); if (tz >= 2400) throw new ParseException("Numeric TimeZone out of range", oindex); int offset = (tz / 100) * 60 + (tz % 100); if (switchSign) { return -offset; } else { return offset; } } /** * will parse the alpha time zone version (e.g. PDT, PST). * the result will be returned in minutes needed to be added * to the date to bring it to GMT. */ public int parseAlphaTimeZone() throws ParseException { int result = 0; boolean foundCommon = false; char curr; try { switch(orig[index++]) { case 'U': case 'u': // "UT" / Universal Time curr = orig[index++]; if (curr == 'T' || curr == 't') { result = 0; break; } throw new ParseException("Bad Alpha TimeZone", index); case 'G': case 'g': // "GMT" ; Universal Time curr = orig[index++]; if (curr == 'M' || curr == 'm') { curr = orig[index++]; if (curr == 'T' || curr == 't') { result = 0; break; } } throw new ParseException("Bad Alpha TimeZone", index); case 'E': case 'e': // "EST" / "EDT" ; Eastern: - 5/ - 4 result = 300; foundCommon = true; break; case 'C': case 'c': // "CST" / "CDT" ; Central: - 6/ - 5 result = 360; foundCommon = true; break; case 'M': case 'm': // "MST" / "MDT" ; Mountain: - 7/ - 6 result = 420; foundCommon = true; break; case 'P': case 'p': // "PST" / "PDT" ; Pacific: - 8/ - 7 result = 480; foundCommon = true; break; default: throw new ParseException("Bad Alpha TimeZone", index); } } catch (ArrayIndexOutOfBoundsException e) { throw new ParseException("Bad Alpha TimeZone", index); } if (foundCommon) { curr = orig[index++]; if (curr == 'S' || curr == 's') { curr = orig[index++]; if (curr != 'T' && curr != 't') { throw new ParseException("Bad Alpha TimeZone", index); } } else if (curr == 'D' || curr == 'd') { curr = orig[index++]; if (curr == 'T' || curr != 't') { // for daylight time result -= 60; } else { throw new ParseException("Bad Alpha TimeZone", index); } } } return result; } int getIndex() { return index; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy