Index: src/test/java/org/apache/pdfbox/util/TestDateUtil.java
===================================================================
--- src/test/java/org/apache/pdfbox/util/TestDateUtil.java	(revision 1566603)
+++ src/test/java/org/apache/pdfbox/util/TestDateUtil.java	(working copy)
@@ -65,35 +65,30 @@
      *
      * @throws Exception when there is an exception
      */
-    public void testExtract() throws Exception
+    public void testToCalendar() throws Exception
     {
-        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
-        }   
+        TimeZone zuluTime = TimeZone.getTimeZone("UTC");
+        
+        GregorianCalendar date = new GregorianCalendar( 2005, 4, 12 );
+        DateConverter.adjustTimeZoneNicely(date, zuluTime);
+        assertCalendarEquals(date, 
+                DateConverter.toCalendar( "D:05/12/2005" ) );
+
+        date = new GregorianCalendar( 2005, 4,12,15,57,16 );
+        DateConverter.adjustTimeZoneNicely(date, zuluTime);
+        assertCalendarEquals( date, 
+                DateConverter.toCalendar( "5/12/2005 15:57:16" ) );
+
+        date = new GregorianCalendar(1902, 4, 7,0,0,0);
+        DateConverter.adjustTimeZoneNicely(date, zuluTime);      
+        assertCalendarEquals(date, DateConverter.toCalendar("7 of May in 1902", 
+                            new String[] {"d 'of' MMM 'in' yyyy"}));
+
+        assertNull(DateConverter.toCalendar((String)null));
+        assertNull(DateConverter.toCalendar((COSString)null));
+        assertNull(DateConverter.toCalendar(""));
+        assertNull(DateConverter.toCalendar(new COSString("")));
+        assertNull(DateConverter.toCalendar("   "));
     }
     
     /**
@@ -213,6 +208,11 @@
             checkParse(2020, 5,26,11,25,10, 0, "26 May 2020 11:25:10");
             checkParse(2021, 5,26,11,23, 0, 0, "26 May 2021 11:23");
 
+            // try null or empty args
+            checkParse(BAD, 0, 0, 0, 0, 0,  0,  null);
+            checkParse(BAD, 0, 0, 0, 0, 0,  0,  "");
+            checkParse(BAD, 0, 0, 0, 0, 0,  0,  "   ");
+
             // try dates invalid due to out of limit values
             checkParse(BAD, 0, 0, 0, 0, 0,  0,  "Tuesday, May 32 2000 11:27 UCT");
             checkParse(BAD, 0, 0, 0, 0, 0,  0,  "32 May 2000 11:25");
@@ -410,11 +410,11 @@
     }
     
     /**
-     * Timezone offset testcase.
+     * Timezone offset test case.
      * 
      * @throws Exception
      */
-    public void testFormatTZoffset()
+    public void testFormatTZoffset() throws Exception
     {
         checkFormatOffset(-12.1, "+11:54");
         checkFormatOffset(12.1, "-11:54");
Index: src/main/java/org/apache/pdfbox/util/DateConverter.java
===================================================================
--- src/main/java/org/apache/pdfbox/util/DateConverter.java	(revision 1566603)
+++ src/main/java/org/apache/pdfbox/util/DateConverter.java	(working copy)
@@ -77,7 +77,7 @@
      * 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;
+    public static final int INVALID_YEAR = 999;   // ("nein, nein, nein")
     
     
     /**
@@ -628,7 +628,7 @@
      * - 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.
+     * @param text The String that may begin with a date. 
      *      Initial spaces and "D:" are skipped over.
      * @param moreFmts Additional formats to be tried after trying the
      *      built-in formats.
@@ -645,7 +645,10 @@
     public static Calendar parseDate(String text, String[] moreFmts, 
             ParsePosition initialWhere) 
     {
-        // place to remember longestr date string
+        if (text == null || text.equals(""))
+            return null;
+        
+        // remember longest date string
         int longestLen = -999999;  // theorem: this value will never be used
                 // proof: longestLen is only used if longestDate is not null
         GregorianCalendar longestDate = null; // null says no date found yet
@@ -740,7 +743,9 @@
      * @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
+     * @deprecated This method creates an unnecessary dependency
+     *      of DateConverter on pdfbox. The method should be removed as part of 
+     *      moving DateConverter to a support package. Replace
      *      calls to it with {@link #toCalendar(String, String[])} 
      *      and test for failure with
      *          (value == null || value.get(Calendar.YEAR) == INVALID_YEAR)
@@ -763,7 +768,8 @@
      * 
      * @param text The string representation of the calendar.
      * @return The Calendar that this string represents 
-     *      or null if the incoming text is null.
+     *      or null if the incoming text is null, the empty string or
+     *      a string with only spaces.
      * @throws IOException If the date string is non-null 
      *      and not a parseable date.
      * @deprecated This method throws an IOException for failure. Replace
@@ -774,12 +780,12 @@
      */
     public static Calendar toCalendar(String text) throws IOException
     {
-        if (text == null)
+        if (text == null || text.equals("") || text.trim().equals(""))
         {
             return null;    
         }
         Calendar val = toCalendar(text, null);
-        if (val != null && val.get(Calendar.YEAR) == INVALID_YEAR)  
+        if (val != null && isBad(val))  
         {
             throw new IOException("Error converting date: " + text);
         }
@@ -795,18 +801,31 @@
      * 
      * @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.
+     *      in addition to the standard list. Example:
+     *          Calendar date = toCalendar("7 of May in 1902", 
+     *                      new String[] {"d of MMM in yyyy"});
+     * @return the Calendar value corresponding to the date text.
+     *      The design is such that DateConverter ALWAYS returns a Calendar,
+     *      even for a null or empty argument. Client code need not test
+     *      for validity if it does not care what the date is.
+     *      If 'text' does not represent a valid date, 
+     *      the value returned is January 1 of year INVALID_YEAR at 0:0:0 GMT.
      * 
      */
     public static Calendar toCalendar(String text, String[] moreFmts)
     {
+        Calendar retCal;
         ParsePosition where = new ParsePosition(0);
-        skipOptionals(text, where, " ");
-        skipString(text, "D:", where);
-        Calendar retCal = parseDate(text, moreFmts, where);   // PARSE THE TEXT
+        if (text == null || "".equals(text) || "".equals(text.trim()))
+        {
+            retCal = null;
+        }
+        else 
+        {
+            skipOptionals(text, where, " ");
+            skipString(text, "D:", where);
+            retCal = parseDate(text, moreFmts, where);   // PARSE THE TEXT
+        }
         if (retCal == null || where.getIndex() != text.length()) 
         {
             // the date string is invalid for all formats we tried,
@@ -815,4 +834,38 @@
         }
         return retCal;
     }
+
+    /**
+    * Returns whether or not the Calendar created by parse
+    * is a bad date.  This is syntactic sugar for the transition
+    * from returning null or a sentinel Calendar value to 
+    * returning only a sentinel Calendar value
+    * 
+    * @param cal Calendar to be checked
+    * @return was this a null or the sentinel bad calendar
+    */
+    public static boolean isBad(Calendar cal)
+    {
+        if (cal == null)
+        {
+            return true;
+        }
+
+        //Sentinel bad is Gregorian
+        if (! (cal instanceof GregorianCalendar))
+        {
+            return false;
+        }
+        if (cal.get(Calendar.YEAR) == INVALID_YEAR &&
+            cal.get(Calendar.MONTH) == 0 &&
+            cal.get(Calendar.DAY_OF_WEEK_IN_MONTH) == 1 &&
+            cal.get(Calendar.HOUR_OF_DAY) == 0 &&
+            cal.get(Calendar.MINUTE) == 0 &&
+            cal.get(Calendar.SECOND) == 0 &&
+            cal.get(Calendar.MILLISECOND) == 0)
+        {
+            return true;
+        }
+        return false;
+    }
 }
