Author: jahewson Date: Thu Feb 13 03:35:20 2014 New Revision: 1567840 URL: http://svn.apache.org/r1567840 Log: PDFBOX-1803 Refactoring of pdfbox.util.DateConverter
Modified: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/cos/COSDictionary.java pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/digitalsignature/PDSignature.java pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/util/DateConverter.java pdfbox/trunk/pdfbox/src/test/java/org/apache/pdfbox/util/TestDateUtil.java Modified: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/cos/COSDictionary.java URL: http://svn.apache.org/viewvc/pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/cos/COSDictionary.java?rev=1567840&r1=1567839&r2=1567840&view=diff ============================================================================== --- pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/cos/COSDictionary.java (original) +++ pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/cos/COSDictionary.java Thu Feb 13 03:35:20 2014 @@ -770,13 +770,12 @@ public class COSDictionary extends COSBa /** * This is a convenience method that will get the dictionary object that * is expected to be a name and convert it to a string. Null is returned - * if the entry does not exist in the dictionary. + * if the entry does not exist in the dictionary or if the date was invalid. * * @param key The key to the item in the dictionary. - * @return The name converted to a string. - * @throws IOException If there is an error converting to a date. + * @return The name converted to a date. */ - public Calendar getDate( String key ) throws IOException + public Calendar getDate( String key ) { return getDate( COSName.getPDFName( key ) ); } @@ -784,14 +783,12 @@ public class COSDictionary extends COSBa /** * This is a convenience method that will get the dictionary object that * is expected to be a name and convert it to a string. Null is returned - * if the entry does not exist in the dictionary. + * if the entry does not exist in the dictionary or if the date was invalid. * * @param key The key to the item in the dictionary. - * @return The name converted to a string. - * - * @throws IOException If there is an error converting to a date. + * @return The name converted to a date. */ - public Calendar getDate( COSName key ) throws IOException + public Calendar getDate( COSName key ) { COSString date = (COSString)getDictionaryObject( key ); return DateConverter.toCalendar( date ); @@ -800,14 +797,13 @@ public class COSDictionary extends COSBa /** * This is a convenience method that will get the dictionary object that * is expected to be a date. Null is returned - * if the entry does not exist in the dictionary. + * if the entry does not exist in the dictionary or if the date was invalid. * * @param key The key to the item in the dictionary. * @param defaultValue The default value to return. - * @return The name converted to a string. - * @throws IOException If there is an error converting to a date. + * @return The name converted to a date. */ - public Calendar getDate( String key, Calendar defaultValue ) throws IOException + public Calendar getDate( String key, Calendar defaultValue ) { return getDate( COSName.getPDFName( key ), defaultValue ); } @@ -815,14 +811,13 @@ public class COSDictionary extends COSBa /** * This is a convenience method that will get the dictionary object that * is expected to be a date. Null is returned - * if the entry does not exist in the dictionary. + * if the entry does not exist in the dictionary or if the date was invalid. * * @param key The key to the item in the dictionary. * @param defaultValue The default value to return. - * @return The name converted to a string. - * @throws IOException If there is an error converting to a date. + * @return The name converted to a date. */ - public Calendar getDate( COSName key, Calendar defaultValue ) throws IOException + public Calendar getDate( COSName key, Calendar defaultValue ) { Calendar retval = getDate( key ); if( retval == null ) Modified: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/digitalsignature/PDSignature.java URL: http://svn.apache.org/viewvc/pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/digitalsignature/PDSignature.java?rev=1567840&r1=1567839&r2=1567840&view=diff ============================================================================== --- pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/digitalsignature/PDSignature.java (original) +++ pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/digitalsignature/PDSignature.java Thu Feb 13 03:35:20 2014 @@ -265,14 +265,7 @@ public class PDSignature implements COSO */ public Calendar getSignDate() { - try - { - return dictionary.getDate(COSName.M); - } - catch (IOException e) - { - return null; - } + return dictionary.getDate(COSName.M); } /** Modified: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/util/DateConverter.java URL: http://svn.apache.org/viewvc/pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/util/DateConverter.java?rev=1567840&r1=1567839&r2=1567840&view=diff ============================================================================== --- pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/util/DateConverter.java (original) +++ pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/util/DateConverter.java Thu Feb 13 03:35:20 2014 @@ -16,7 +16,6 @@ */ package org.apache.pdfbox.util; -import java.io.IOException; import java.text.ParsePosition; import java.text.SimpleDateFormat; import java.util.Calendar; @@ -28,59 +27,52 @@ import java.util.TimeZone; import org.apache.pdfbox.cos.COSString; -/** +/* * Date format is described in PDF Reference 1.7 section 3.8.2 * (www.adobe.com/devnet/acrobat/pdfs/pdf_reference_1-7.pdf) - * and also in PDF 32000-1:2008 + * and also in PDF 32000-1:2008 * (http://www.adobe.com/devnet/acrobat/pdfs/PDF32000_2008.pdf)) * although the latter inexplicably omits the trailing apostrophe. - * - * The interpretation of dates without timezones is unclear. + * + * The interpretation of dates without timezones is unclear. * The code below assumes that such dates are in UTC+00 (aka GMT). * This is in keeping with the PDF Reference's assertion that: - * numerical fields default to zero values. + * numerical fields default to zero values. * However, the Reference does go on to make the cryptic remark: - * If no UT information is specified, the relationship of the specified - * time to UT is considered to be unknown. Whether or not the time + * If no UT information is specified, the relationship of the specified + * time to UT is considered to be unknown. Whether or not the time * zone is known, the rest of the date should be specified in local time. - * I understand this to refer to _creating_ a pdf date value. That is, - * code that can get the wall clock time and cannot get the timezone + * I understand this to refer to _creating_ a pdf date value. That is, + * code that can get the wall clock time and cannot get the timezone * should write the wall clock time with a time zone of zero. * When _parsing_ a PDF date, the statement talks about "the rest of the date" * being local time, thus explicitly excluding the use of the local time * for the time zone. -*/ +*/ /** - * This class is used to convert dates to strings and back using the PDF - * date standard in section 3.8.2 of PDF Reference 1.7. + * Converts dates to strings and back using the PDF date standard + * in section 3.8.2 of PDF Reference 1.7. * - * @author <a href="mailto:b...@benlitchfield.com">Ben Litchfield</a> - * @author <a href="mailto:zweibie...@ahoo.com">Fred Hansen</a> + * @author Ben Litchfield + * @author Fred Hansen * * TODO Move members of this class elsewhere for shared use in pdfbox, xmpbox, and jempbox. */ -public class DateConverter +public final class DateConverter { + private DateConverter() + { + } + // milliseconds/1000 = seconds; seconds / 60 = minutes; minutes/60 = hours private static final int MINUTES_PER_HOUR = 60; private static final int SECONDS_PER_MINUTE = 60; private static final int MILLIS_PER_MINUTE = SECONDS_PER_MINUTE*1000; private static final int MILLIS_PER_HOUR = MINUTES_PER_HOUR * MILLIS_PER_MINUTE; - private static final int - HALF_DAY = 12 * MINUTES_PER_HOUR * MILLIS_PER_MINUTE, - DAY = 2*HALF_DAY; - - /** - * Error value if date is invalid. Parsing is done with - * GregorianCalendar.setLenient(false), so every date field value - * must be within bounds. If an attempt is made to parse an invalid date - * field, toCalendar(String, String[]) returns Jan 1 in year INVALID_YEAR. - */ - public static final int INVALID_YEAR = 999; - - - /** + private static final int HALF_DAY = 12 * MINUTES_PER_HOUR * MILLIS_PER_MINUTE, DAY = 2*HALF_DAY; + + /* * The Date format is supposed to be the PDF_DATE_FORMAT, but other * forms appear. These lists offer alternatives to be tried * if parseBigEndianDate fails. @@ -110,13 +102,13 @@ public class DateConverter */ private static final String[] ALPHA_START_FORMATS = { - "EEEE, dd MMM yy hh:mm:ss a", - "EEEE, MMM dd, yy hh:mm:ss a", - "EEEE, MMM dd, yy 'at' hh:mma", // Acrobat Net Distiller 1.0 for Windows - "EEEE, MMM dd, yy", // Acrobat Distiller 1.0.2 for Macintosh && PDFBOX-465 - "EEEE MMM dd, yy HH:mm:ss", // ECMP5 - "EEEE MMM dd HH:mm:ss z yy", // GNU Ghostscript 7.0.7 - "EEEE MMM dd HH:mm:ss yy", // GNU Ghostscript 7.0.7 variant + "EEEE, dd MMM yy hh:mm:ss a", + "EEEE, MMM dd, yy hh:mm:ss a", + "EEEE, MMM dd, yy 'at' hh:mma", // Acrobat Net Distiller 1.0 for Windows + "EEEE, MMM dd, yy", // Acrobat Distiller 1.0.2 for Macintosh && PDFBOX-465 + "EEEE MMM dd, yy HH:mm:ss", // ECMP5 + "EEEE MMM dd HH:mm:ss z yy", // GNU Ghostscript 7.0.7 + "EEEE MMM dd HH:mm:ss yy", // GNU Ghostscript 7.0.7 variant }; private static final String[] DIGIT_START_FORMATS = @@ -155,28 +147,6 @@ public class DateConverter // '' mapped to a single ', and the ''' was invalid) }; - - private DateConverter() - { - //utility class should not be constructed. - } - - //////////////////////////////////////////// - // C o n v e r t t o S t r i n g Methods - - /** - * Get all know formats. - * - * @return an array containig all known formats - */ - public static String[] getFormats() - { - String[] val = new String[ALPHA_START_FORMATS.length+DIGIT_START_FORMATS.length]; - System.arraycopy(ALPHA_START_FORMATS, 0, val, 0, ALPHA_START_FORMATS.length); - System.arraycopy(DIGIT_START_FORMATS, 0, val,ALPHA_START_FORMATS.length, DIGIT_START_FORMATS.length); - return val; - } - /** * Converts a Calendar to a string formatted as: * D:yyyyMMddHHmmss#hh'mm' where # is Z, +, or -. @@ -193,14 +163,14 @@ public class DateConverter { return null; } - String offset = formatTZoffset(cal.get(Calendar.ZONE_OFFSET) - + cal.get(Calendar.DST_OFFSET), "'"); + String offset = formatTZoffset(cal.get(Calendar.ZONE_OFFSET) + + cal.get(Calendar.DST_OFFSET), "'"); return String.format("D:" + "%1$4tY%1$2tm%1$2td" // yyyyMMdd + "%1$2tH%1$2tM%1$2tS" // HHmmss - + "%2$s" // time zone - + "'", // trailing apostrophe - cal, offset); + + "%2$s" // time zone + + "'", // trailing apostrophe + cal, offset); } /** @@ -214,34 +184,32 @@ public class DateConverter */ public static String toISO8601(Calendar cal) { - String offset = formatTZoffset(cal.get(Calendar.ZONE_OFFSET) - + cal.get(Calendar.DST_OFFSET), ":"); + String offset = formatTZoffset(cal.get(Calendar.ZONE_OFFSET) + + cal.get(Calendar.DST_OFFSET), ":"); return String.format( - "%1$4tY" // yyyy - + "-%1$2tm" // -mm (%tm adds one to cal month value) - + "-%1$2td" // -dd (%tm adds one to cal month value) - + "T" // T - + "%1$2tH:%1$2tM:%1$2tS" // HHmmss - + "%2$s", // time zone - cal, offset); + "%1$4tY" // yyyy + + "-%1$2tm" // -mm (%tm adds one to cal month value) + + "-%1$2td" // -dd (%tm adds one to cal month value) + + "T" // T + + "%1$2tH:%1$2tM:%1$2tS" // HHmmss + + "%2$s", // time zone + cal, offset); } - /** + /* * Constrain a timezone offset to the range [-11:59 thru +11:59]. - * @param proposedOffset A value intended to be a timezone offset. - * @return The corresponding value reduced to the above noted range * by adding or subtracting multiples of a full day. */ - public static int restrainTZoffset(long proposedOffset) + private static int restrainTZoffset(long proposedOffset) { - proposedOffset = ((proposedOffset+HALF_DAY)%DAY+DAY)%DAY; + proposedOffset = ((proposedOffset + HALF_DAY) % DAY + DAY) % DAY; // 0 <= proposedOffset < DAY - proposedOffset = (proposedOffset-HALF_DAY)%HALF_DAY; + proposedOffset = (proposedOffset - HALF_DAY) % HALF_DAY; // -HALF_DAY < proposedOffset < HALF_DAY return (int)proposedOffset; } - /** + /* * Formats a time zone offset as #hh^mm * where # is + or -, hh is hours, ^ is a separator, and mm is minutes. * Any separator may be specified by the second argument; @@ -250,7 +218,7 @@ public class DateConverter * For offset of 0 millis, the String returned is "+00^00", never "Z". * To get a "general" offset in form GMT#hh:mm, write * "GMT"+DateConverter.formatTZoffset(offset, ":"); - * <p> + * * Take thought in choosing the source for the millis value. * It can come from calendarValue.getTimeZone() or from * calendarValue.get(Calendar.ZONE_OFFSET). If a TimeZone was created @@ -261,7 +229,7 @@ public class DateConverter * will not have a daylight savings rule. (Not even if there is a * known time zone with the given ID. To get the TimeZone named "xDT" * with its DST rule, use an ID of EST5EDT, CST6CDT, MST7MDT, or PST8PDT. - * <p> + * * When parsing PDF dates, the incoming values DOES NOT have a TIMEZONE value. * At most it has an OFFSET value like -04'00'. It is generally impossible to * determine what TIMEZONE corresponds to a given OFFSET. If the date is @@ -270,11 +238,11 @@ public class DateConverter * offset -0400 and no daylight saving. Or it might correspond to * any one of the 31 regions (out of 43) that observe daylight savings * and have standard time offset of -0500. - * <p> + * * If a Calendar has not been assigned a TimeZone with setTimeZone(), * it will have by default the local TIMEZONE, not just the OFFSET. In the * USA, this TimeZone will have a daylight savings rule. - * <p> + * * The offset assigned with calVal.set(Calendar.ZONE_OFFSET) differs * from the offset in the TimeZone set by Calendar.setTimeZone(). Example: * Suppose my local TimeZone is America/New_York. It has an offset of -05'00'. @@ -286,7 +254,7 @@ public class DateConverter * calVal.get(Calendar.ZONE_OFFSET) + calVal.get(Calendar.DST_OFFSET) => -06:00 * calVal.getTimeZone().getRawOffset() => -05:00 * calVal.getTimeZone().getOffset(calVal.getTimeInMillis()) => -04:00 - * <p> + * * Which is correct??? I dunno, though setTimeZone() does seem to affect * ZONE_OFFSET, and not vice versa. One cannot even test whether TimeZone * or ZONE_OFFSET has been set; both have been set by initialization code. @@ -295,63 +263,54 @@ public class DateConverter * * My choice in this DateConverter class has been to set the * initial TimeZone of a GregorianCalendar to GMT. Thereafter - * the TimeZone is modified with {@link #adjustTimeZoneNicely}. - * - * @param millis a time zone offset expressed in milliseconds - * Any value is accepted; it is normalized to [-11:59 ... +11:59] - * @param sep a String to insert between hh and mm. May be empty. - * @return the formatted String for the offset + * the TimeZone is modified with {@link #adjustTimeZoneNicely}. + * + * package-private for testing */ - public static String formatTZoffset(long millis, String sep) + static String formatTZoffset(long millis, String sep) { SimpleDateFormat sdf = new SimpleDateFormat("Z"); // #hhmm sdf.setTimeZone(new SimpleTimeZone(restrainTZoffset(millis),"unknown")); String tz = sdf.format(new Date()); - return tz.substring(0,3)+sep+tz.substring(3); + return tz.substring(0,3) + sep + tz.substring(3); } - ////////////////////////////////////////////// - // P A R S E Methods - - /** + /* * Parses an integer from a string, starting at and advancing a ParsePosition. - * - * @param text The string being parsed. If null, the remedy value is returned. - * @param where The ParsePosition to start the search. This value - * will be incremented by the number of digits found, but no - * more than maxlen. That is, the ParsePosition will - * advance across at most maxlen initial digits in text. - * The error index is ignored and unchanged. - * @param maxlen The maximum length of the integer to parse. - * Usually 2, but 4 for year fields. - * If the field of length maxlen begins with a digit, - * but contains a non-digit, no error is signaled - * and the integer value is returned. - * @param remedy Value to be assigned if no digit is found at the - * initial parse position; that is, if the field is empty. - * @return The integer that was at the given parse position. Or - * the remedy value if no digits were found. + * Returns The integer that was at the given parse position, or the remedy value + * if no digits were found. + * + * The ParsePosition will be incremented by the number of digits found, but no + * more than maxlen. That is, the ParsePosition will advance across at most + * maxlen initial digits in text. The error index is ignored and unchanged. + * + * maxlen is the maximum length of the integer to parse, usually 2, but 4 for + * year fields. If the field of length maxlen begins with a digit, but contains + * a non-digit, no error is signaled and the integer value is returned. */ - public static int parseTimeField(String text, ParsePosition where, - int maxlen, int remedy) + private static int parseTimeField(String text, ParsePosition where, int maxlen, int remedy) { if (text == null) { - return remedy; + return remedy; } - // (it would seem that DecimalFormat.parse() would be simpler; - // but that class blithely ignores setMaximumIntegerDigits) + // it would seem that DecimalFormat.parse() would be simpler; + // but that class blithely ignores setMaximumIntegerDigits int retval = 0; int index = where.getIndex(); int limit = index + Math.min(maxlen, text.length()-index); for (; index < limit; index++) { - int cval = text.charAt(index) - '0'; // convert digit to integer - if (cval <0 || cval > 9) // test to see if we got a digit + // convert digit to integer + int cval = text.charAt(index) - '0'; + // test to see if we got a digit + if (cval < 0 || cval > 9) { - break; // no digit at index + // no digit at index + break; } - retval = retval*10 + cval; // append the digit to the return value + // append the digit to the return value + retval = retval * 10 + cval; } if (index == where.getIndex()) { @@ -361,28 +320,21 @@ public class DateConverter return retval; } - /** - * Advances the ParsePosition past any and all the characters - * that match those in the optionals list. - * In particular, a space will skip all spaces. - * @param text The text to examine - * @param where index to start looking. - * The value is incremented by the number of optionals found. - * The error index is ignored and unchanged. - * @param optionals A String listing all the optional characters - * to be skipped. - * @return The last non-space character passed over. - * Returns a space if no non-space character was found - * (even if space is not in the optionals list.) + /* + * Advances the ParsePosition past any and all the characters that match + * those in the optionals list. In particular, a space will skip all spaces. + * + * The start value is incremented by the number of optionals found. The error + * index is ignored and unchanged. + * + * Returns the last non-space character passed over (even if space is not in + * the optionals list.) */ - public static char skipOptionals(String text, ParsePosition where, - String optionals) + private static char skipOptionals(String text, ParsePosition where, String optionals) { char retval = ' ', currch; - while (text != null && where.getIndex() < text.length() - && optionals.indexOf( - (currch=text.charAt(where.getIndex())) - ) >= 0) + while (text != null && where.getIndex() < text.length() && + optionals.indexOf((currch = text.charAt(where.getIndex()))) >= 0) { retval = (currch != ' ') ? currch : retval; where.setIndex(where.getIndex() + 1); @@ -390,18 +342,15 @@ public class DateConverter return retval; } - /** - * If the victim string is at the given position in the text, - * this method advances the position past that string. - * - * @param text The text to examine - * @param victim The string to look for - * @param where The initial position to look at. After return, this will - * have been incremented by the length of the victim if it was found. - * The error index is ignored and unchanged. - * @return true if victim was found; otherwise false. + /* + * If the victim string is at the given position in the text, this method + * advances the position past that string. + * + * `where` is the initial position to look at. After return, this will have + * been incremented by the length of the victim if it was found. The error + * index is ignored and unchanged. */ - public static boolean skipString(String text, String victim, ParsePosition where) + private static boolean skipString(String text, String victim, ParsePosition where) { if (text.startsWith(victim, where.getIndex())) { @@ -411,15 +360,15 @@ public class DateConverter return false; } - /** + /* * Construct a new GregorianCalendar and set defaults. * Locale is ENGLISH. * TimeZone is "UTC" (zero offset and no DST). * Parsing is NOT lenient. Milliseconds are zero. - * - * @return a new gregorian calendar + * + * package-private for testing */ - public static GregorianCalendar newGreg() + static GregorianCalendar newGreg() { GregorianCalendar retCal = new GregorianCalendar(Locale.ENGLISH); retCal.setTimeZone(new SimpleTimeZone(0, "UTC")); @@ -428,23 +377,21 @@ public class DateConverter return retCal; } - /** + /* * Install a TimeZone on a GregorianCalendar without changing the * hours value. A plain GregorianCalendat.setTimeZone() * adjusts the Calendar.HOUR value to compensate. This is *BAD* * (not to say *EVIL*) when we have already set the time. - * @param cal The GregorianCalendar whose TimeZone to change. - * @param tz The new TimeZone. */ - public static void adjustTimeZoneNicely(GregorianCalendar cal, TimeZone tz) + private static void adjustTimeZoneNicely(GregorianCalendar cal, TimeZone tz) { cal.setTimeZone(tz); - int offset = (cal.get(Calendar.ZONE_OFFSET) + cal.get(Calendar.DST_OFFSET)) - / MILLIS_PER_HOUR; + int offset = (cal.get(Calendar.ZONE_OFFSET) + cal.get(Calendar.DST_OFFSET)) / + MILLIS_PER_HOUR; cal.add(Calendar.HOUR, -offset); } - /** + /* * Parses the end of a date string for a time zone and, if one is found, * sets the time zone of the GregorianCalendar. Otherwise the calendar * time zone is unchanged. @@ -457,26 +404,22 @@ public class DateConverter * This scheme accepts the format of PDF, RFC 822, and ISO8601. * If none of these applies (as for a time zone name), we try * TimeZone.getTimeZone(). - * - * @param text The text expected to begin with a time zone value, - * possibly with leading or trailing spaces. - * @param cal The Calendar whose TimeZone to set. - * @param initialWhere where Scanning begins at where.index. After success, the returned - * index is that of the next character after the recognized string. - * The error index is ignored and unchanged. - * @return true if parsed a time zone value; otherwise the - * time zone is unchanged and the return value is false. + * + * Scanning begins at where.index. After success, the returned index + * is that of the next character after the recognized string. + * + * package-private for testing */ - public static boolean parseTZoffset(String text, GregorianCalendar cal, - ParsePosition initialWhere) + static boolean parseTZoffset(String text, GregorianCalendar cal, + ParsePosition initialWhere) { ParsePosition where = new ParsePosition(initialWhere.getIndex()); TimeZone tz = new SimpleTimeZone(0, "GMT"); int tzHours, tzMin; char sign = skipOptionals(text, where, "Z+- "); - boolean hadGMT = (sign == 'Z' || skipString(text, "GMT", where) - || skipString(text, "UTC", where)); - sign = ( ! hadGMT) ? sign : skipOptionals(text, where, "+- "); + boolean hadGMT = (sign == 'Z' || skipString(text, "GMT", where) || + skipString(text, "UTC", where)); + sign = (!hadGMT) ? sign : skipOptionals(text, where, "+- "); tzHours = parseTimeField(text, where, 2, -999); skipOptionals(text, where, "\': "); @@ -484,23 +427,27 @@ public class DateConverter skipOptionals(text, where, "\' "); if (tzHours != -999) - { // we parsed a time zone in default format - int hrSign = (sign == '-' ? -1 :+1); - tz.setRawOffset(restrainTZoffset(hrSign*(tzHours*MILLIS_PER_HOUR + tzMin*MILLIS_PER_MINUTE))); + { + // we parsed a time zone in default format + int hrSign = (sign == '-' ? -1 : 1); + tz.setRawOffset(restrainTZoffset(hrSign * (tzHours * MILLIS_PER_HOUR + tzMin * + MILLIS_PER_MINUTE))); tz.setID("unknown"); } else if ( ! hadGMT) - { // try to process as a name; "GMT" or "UTC" has already been processed + { + // try to process as a name; "GMT" or "UTC" has already been processed String tzText = text.substring(initialWhere.getIndex()).trim(); tz = TimeZone.getTimeZone(tzText); // getTimeZone returns "GMT" for unknown ids if ("GMT".equals(tz.getID())) - { // no timezone in text - // cal amd initialWhere are unchanged + { + // no timezone in text, cal amd initialWhere are unchanged return false; } else - { // we got a tz by name; use it + { + // we got a tz by name; use it where.setIndex(text.length()); } } @@ -509,9 +456,9 @@ public class DateConverter return true; } - /** + /* * Parses a big-endian date: year month day hour min sec. - * The year must be four digits. Other fields may be adjacent + * The year must be four digits. Other fields may be adjacent * and delimited by length or they may follow appropriate delimiters. * year [ -/]* month [ -/]* dayofmonth [ T]* hour [:] min [:] sec [.secFraction] * If any numeric field is omitted, all following fields must also be omitted. @@ -520,16 +467,11 @@ public class DateConverter * Ambiguous dates can produce unexpected results. For example: * 1970 12 23:08 will parse as 1970 December 23 00:08:00 * - * @param text The string to parse. - * - * @param initialWhere Where to begin the parse. On return the index - * is advanced to just beyond the last character processed. - * The error index is ignored and unchanged. - * - * @return a GregorianCalendar representing the parsed date. - * Or null if the text did not begin with at least four digits. + * The parse begins at `where, on return the index + * is advanced to just beyond the last character processed. + * The error index is ignored and unchanged. */ - public static GregorianCalendar parseBigEndianDate(String text, + private static GregorianCalendar parseBigEndianDate(String text, ParsePosition initialWhere) { ParsePosition where = new ParsePosition(initialWhere.getIndex()); @@ -559,7 +501,8 @@ public class DateConverter try { dest.set(year, month, day, hour, minute, second); - dest.getTimeInMillis(); // trigger limit tests + // trigger limit tests + dest.getTimeInMillis(); } catch (IllegalArgumentException ill) { @@ -567,30 +510,25 @@ public class DateConverter } initialWhere.setIndex(where.getIndex()); skipOptionals(text, initialWhere, " "); - return dest; // dest has at least a year value + // dest has at least a year value + return dest; } - /** - * See if text can be parsed as a date according to any of a list of + /* + * See if text can be parsed as a date according to any of a list of * formats. The time zone may be included as part of the format, or * omitted in favor of later testing for a trailing time zone. * - * @param text The text to be parsed. - * - * @param fmts A list of formats to be tried. The syntax is that for - * {@link java.text.SimpleDateFormat} - * - * @param initialWhere At start this is the position to begin - * examining the text. Upon return it will have been - * incremented to refer to the next non-space character after the date. - * If no date was found, the value is unchanged. - * The error index is ignored and unchanged. - * - * @return null for failure to find a date, or the GregorianCalendar - * for the date that was found. Unless a time zone was - * part of the format, the time zone will be GMT+0 + * The parse starts at `where`, upon return it will have been + * incremented to refer to the next non-space character after the date. + * If no date was found, the value is unchanged. + * The error index is ignored and unchanged. + * + * If there is a failure to find a date, or the GregorianCalendar + * for the date that was found. Unless a time zone was + * part of the format, the time zone will be GMT+0 */ - public static GregorianCalendar parseSimpleDate(String text, String[] fmts, + private static GregorianCalendar parseSimpleDate(String text, String[] fmts, ParsePosition initialWhere) { for(String fmt : fmts) @@ -609,8 +547,7 @@ public class DateConverter return null; } - - /** + /* * Parses a String to see if it begins with a date, and if so, * returns that date. The date must be strictly correct--no * field may exceed the appropriate limit. @@ -627,27 +564,19 @@ public class DateConverter * - PDF format dates are among those recognized by parseBigEndianDate. * - The formats tried are alphaStartFormats or digitStartFormat and * any listed in the value of moreFmts. - * - * @param text The String that may begin with a date. Must not be null. - * Initial spaces and "D:" are skipped over. - * @param moreFmts Additional formats to be tried after trying the - * built-in formats. - * @param initialWhere where Parsing begins at the given position in text. If the - * parse succeeds, the index of where is advanced to point - * to the first unrecognized character. - * The error index is ignored and unchanged. - * @return A GregorianCalendar for the date. If no date is found, - * returns null. The time zone will be GMT+0 unless parsing - * succeeded with a format containing a time zone. (Only one - * builtin format contains a time zone.) - * */ - public static Calendar parseDate(String text, String[] moreFmts, - ParsePosition initialWhere) + private static Calendar parseDate(String text, ParsePosition initialWhere) { - // place to remember longestr date string - int longestLen = -999999; // theorem: this value will never be used - // proof: longestLen is only used if longestDate is not null + if (text == null || text.equals("")) + { + return null; + } + + // remember longestr date string + int longestLen = -999999; + // theorem: the above value will never be used + // proof: longestLen is only used if longestDate is not null + GregorianCalendar longestDate = null; // null says no date found yet int whereLen; // tempcopy of where.getIndex() @@ -659,12 +588,10 @@ public class DateConverter // try big-endian parse GregorianCalendar retCal = parseBigEndianDate(text, where); // check for success and a timezone - if (retCal != null && - (where.getIndex() == text.length() - || parseTZoffset(text, retCal, where))) + if (retCal != null && (where.getIndex() == text.length() || + parseTZoffset(text, retCal, where))) { - // if text is fully consumed, return the date - // else remember it and its length + // if text is fully consumed, return the date else remember it and its length whereLen = where.getIndex(); if (whereLen == text.length()) { @@ -683,12 +610,11 @@ public class DateConverter : ALPHA_START_FORMATS; retCal = parseSimpleDate(text, formats, where); // check for success and a timezone - if (retCal != null && - (where.getIndex() == text.length() - || parseTZoffset(text, retCal, where))) + if (retCal != null && + (where.getIndex() == text.length() || + parseTZoffset(text, retCal, where))) { - // if text is fully consumed, return the date - // else remember it and its length + // if text is fully consumed, return the date else remember it and its length whereLen = where.getIndex(); if (whereLen == text.length()) { @@ -701,27 +627,7 @@ public class DateConverter longestDate = retCal; } } - - // try the supplied formats - if (moreFmts != null) - { - where.setIndex(startPosition); - retCal = parseSimpleDate(text, moreFmts, where); - if (retCal != null && - (where.getIndex() == text.length() - || parseTZoffset(text, retCal, where))) - { - whereLen = where.getIndex(); - // if text is fully consumed, return the date - // else remember it and its length - if (whereLen == text.length() || - (longestDate != null && whereLen > longestLen)) - { - initialWhere.setIndex(whereLen); - return retCal; - } - } - } + if (longestDate != null) { initialWhere.setIndex(longestLen); @@ -731,21 +637,15 @@ public class DateConverter } /** - * Converts a string to a Calendar by parsing the String for a date. - * @see #toCalendar(String). + * Returns the Calendar for a given COS string containing a date, + * or {@code null} if it cannot be parsed. * * The returned value will have 0 for DST_OFFSET. * - * @param text The COSString representation of a date. - * @return The Calendar that the text string represents. - * Or null if text was null. - * @throws IOException If the date string is not in the correct format. - * @deprecated This method throws an IOException for failure. Replace - * calls to it with {@link #toCalendar(String, String[])} - * and test for failure with - * (value == null || value.get(Calendar.YEAR) == INVALID_YEAR) + * @param text A COS string containing a date. + * @return The Calendar that the text string represents, or {@code null} if it cannot be parsed. */ - public static Calendar toCalendar(COSString text) throws IOException + public static Calendar toCalendar(COSString text) { if (text == null) { @@ -753,66 +653,33 @@ public class DateConverter } return toCalendar(text.getString()); } - + /** - * Converts a string date to a Calendar date value; equivalent to - * {@link #toCalendar(String, String[])} using <pre>null</pre> for the second parameter, - * but throws an IOException for failure. - * + * Returns the Calendar for a given string containing a date, + * or {@code null} if it cannot be parsed. + * * The returned value will have 0 for DST_OFFSET. - * - * @param text The string representation of the calendar. - * @return The Calendar that this string represents - * or null if the incoming text is null. - * @throws IOException If the date string is non-null - * and not a parseable date. - * @deprecated This method throws an IOException for failure. Replace - * calls to it with {@link #toCalendar(String, String[])} - * using <pre>null</pre> for the second parameter - * and test for failure with - * (value == null || value.get(Calendar.YEAR) == INVALID_YEAR) + * + * @param text A COS string containing a date. + * @return The Calendar that the text string represents, or {@code null} if it cannot be parsed. */ - public static Calendar toCalendar(String text) throws IOException + public static Calendar toCalendar(String text) { - if (text == null) - { - return null; - } - Calendar val = toCalendar(text, null); - if (val != null && val.get(Calendar.YEAR) == INVALID_YEAR) + if (text == null || text.trim().equals("")) { - throw new IOException("Error converting date: " + text); + return null; } - return val; - } - - /** - * Converts a string to a calendar. The entire string must be consumed. - * The date must be strictly correct; that is, no field may exceed - * the appropriate limit. Uses {@link #parseDate} to do the actual parsing. - * - * The returned value will have 0 for DST_OFFSET. - * - * @param text The text to parse. Initial spaces and "D:" are skipped over. - * @param moreFmts An Array of formats (as Strings) to try - * in addition to the standard list. - * @return the Calendar value corresponding to the date text. - * If text does not represent a valid date, - * the value is January 1 on year INVALID_YEAR at 0:0:0 GMT. - * - */ - public static Calendar toCalendar(String text, String[] moreFmts) - { + ParsePosition where = new ParsePosition(0); skipOptionals(text, where, " "); skipString(text, "D:", where); - Calendar retCal = parseDate(text, moreFmts, where); // PARSE THE TEXT - if (retCal == null || where.getIndex() != text.length()) + Calendar calendar = parseDate(text, where); + + if (calendar == null || where.getIndex() != text.length()) { - // the date string is invalid for all formats we tried, - retCal = newGreg(); - retCal.set(INVALID_YEAR, 0, 1, 0, 0, 0); + // the date string is invalid + return null; } - return retCal; + return calendar; } } Modified: pdfbox/trunk/pdfbox/src/test/java/org/apache/pdfbox/util/TestDateUtil.java URL: http://svn.apache.org/viewvc/pdfbox/trunk/pdfbox/src/test/java/org/apache/pdfbox/util/TestDateUtil.java?rev=1567840&r1=1567839&r2=1567840&view=diff ============================================================================== --- pdfbox/trunk/pdfbox/src/test/java/org/apache/pdfbox/util/TestDateUtil.java (original) +++ pdfbox/trunk/pdfbox/src/test/java/org/apache/pdfbox/util/TestDateUtil.java Thu Feb 13 03:35:20 2014 @@ -69,31 +69,15 @@ public class TestDateUtil extends TestCa { TimeZone timezone = TimeZone.getDefault(); TimeZone.setDefault(TimeZone.getTimeZone("UTC")); - try - { - assertCalendarEquals( new GregorianCalendar( 2005, 4, 12 ), - DateConverter.toCalendar( "D:05/12/2005" ) ); - assertCalendarEquals( new GregorianCalendar( 2005, 4,12,15,57,16 ), - DateConverter.toCalendar( "5/12/2005 15:57:16" ) ); - } - catch (IOException ex) - { - ex.printStackTrace(); - } - finally - { - TimeZone.setDefault(timezone); - } - // check that new toCalendar gives NullPointer for a null arg - try - { - DateConverter.toCalendar(null, null); - assertNotNull(null); // failed to have expected exception - } - catch (NullPointerException ex) - { - // expected outcome - } + + assertCalendarEquals( new GregorianCalendar( 2005, 4, 12 ), + DateConverter.toCalendar( "D:05/12/2005" ) ); + assertCalendarEquals( new GregorianCalendar( 2005, 4,12,15,57,16 ), + DateConverter.toCalendar( "5/12/2005 15:57:16" ) ); + + TimeZone.setDefault(timezone); + // check that new toCalendarSTATIC gives null for a null arg + assertNull(DateConverter.toCalendar((String)null)); } /** @@ -128,7 +112,7 @@ public class TestDateUtil extends TestCa } /** - * Check toCalendar. + * Check toCalendarSTATIC. * @param yr expected year value * If an IOException is the expected result, yr should be null * @param mon expected month value @@ -136,7 +120,7 @@ public class TestDateUtil extends TestCa * @param hr expected hour value * @param min expected minute value * @param sec expected second value - * @param tz represents expected timezone offset + * @param offset represents expected timezone offset * @param orig A date to be parsed. * @throws Exception If an unexpected error occurs. */ @@ -149,25 +133,17 @@ public class TestDateUtil extends TestCa String iso8601Date = String.format("%04d-%02d-%02d" + "T%02d:%02d:%02d%+03d:00", yr,mon,day,hr,min,sec,offset); - Calendar cal = null; - try - { - cal = DateConverter.toCalendar(orig); - } - catch (IOException ex) - { - assertEquals(yr, BAD); - } + Calendar cal = DateConverter.toCalendar(orig); if (cal != null) { assertEquals(iso8601Date, DateConverter.toISO8601(cal)); assertEquals(pdfDate, DateConverter.toString(cal)); } - // new toCalendar() - cal = DateConverter.toCalendar(orig, null); + // new toCalendarSTATIC() + cal = DateConverter.toCalendar(orig); if (yr == BAD) { - assertEquals(cal.get(Calendar.YEAR), DateConverter.INVALID_YEAR); + assertEquals(null, cal); } else { @@ -368,8 +344,8 @@ public class TestDateUtil extends TestCa checkToString(1991, 11, 1, 1, 14, 15, tzMcMurdo, +0); checkToString(1992, 12, 1, 1, 14, 15, tzMcMurdo, +0); } - - private static void checkParseTZ(int expect, String src) + + private static void checkParseTZ(int expect, String src) { GregorianCalendar dest = DateConverter.newGreg(); DateConverter.parseTZoffset(src, dest, new ParsePosition(0));