]> source.dussan.org Git - poi.git/commitdiff
63779 Add support for the new Java date/time API added in Java 8
authorDominik Stadler <centic@apache.org>
Wed, 9 Oct 2019 19:12:59 +0000 (19:12 +0000)
committerDominik Stadler <centic@apache.org>
Wed, 9 Oct 2019 19:12:59 +0000 (19:12 +0000)
Deprecate HSSFDateUtil
Closes #160 on Github

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1868198 13f79535-47bb-0310-9956-ffa450edef68

16 files changed:
src/java/org/apache/poi/hssf/usermodel/DVConstraint.java
src/java/org/apache/poi/hssf/usermodel/HSSFCell.java
src/java/org/apache/poi/hssf/usermodel/HSSFDateUtil.java
src/java/org/apache/poi/ss/usermodel/Cell.java
src/java/org/apache/poi/ss/usermodel/CellBase.java
src/java/org/apache/poi/ss/usermodel/DateUtil.java
src/ooxml/java/org/apache/poi/xssf/streaming/SXSSFCell.java
src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFCell.java
src/testcases/org/apache/poi/hssf/usermodel/TestCellStyle.java
src/testcases/org/apache/poi/hssf/usermodel/TestFormulas.java
src/testcases/org/apache/poi/hssf/usermodel/TestHSSFDateUtil.java
src/testcases/org/apache/poi/hssf/usermodel/TestReadWriteChart.java
src/testcases/org/apache/poi/ss/formula/atp/TestYearFracCalculatorFromSpreadsheet.java
src/testcases/org/apache/poi/ss/formula/functions/TestDays360.java
src/testcases/org/apache/poi/ss/usermodel/BaseTestCell.java
src/testcases/org/apache/poi/ss/usermodel/TestDateUtil.java

index f3f5e557a49ef976737fa2cafdf8d281ca1e5cfc..5317969278270dec467dfbc17972344838e40e89 100644 (file)
@@ -31,6 +31,7 @@ import org.apache.poi.ss.formula.ptg.NumberPtg;
 import org.apache.poi.ss.formula.ptg.Ptg;
 import org.apache.poi.ss.formula.ptg.StringPtg;
 import org.apache.poi.ss.usermodel.DataValidationConstraint;
+import org.apache.poi.ss.usermodel.DateUtil;
 import org.apache.poi.util.LocaleUtil;
 
 /**
@@ -242,7 +243,7 @@ public class DVConstraint implements DataValidationConstraint {
                if (timeStr == null) {
                        return null;
                }
-               return Double.valueOf(HSSFDateUtil.convertTime(timeStr));
+               return Double.valueOf(DateUtil.convertTime(timeStr));
        }
        /**
         * @param dateFormat pass <code>null</code> for default YYYYMMDD
@@ -254,7 +255,7 @@ public class DVConstraint implements DataValidationConstraint {
                }
                Date dateVal; 
                if (dateFormat == null) {
-                       dateVal = HSSFDateUtil.parseYYYYMMDDDate(dateStr);
+                       dateVal = DateUtil.parseYYYYMMDDDate(dateStr);
                } else {
                        try {
                                dateVal = dateFormat.parse(dateStr);
@@ -263,7 +264,7 @@ public class DVConstraint implements DataValidationConstraint {
                                                + "' using specified format '" + dateFormat + "'", e);
                        }
                }
-               return Double.valueOf(HSSFDateUtil.getExcelDate(dateVal));
+               return Double.valueOf(DateUtil.getExcelDate(dateVal));
        }
 
        public static DVConstraint createCustomFormulaConstraint(String formula) {
index 12788d5bf9736331b5f35b984047977dbf705e1d..7b309fe54f04420c0b3134edddd8572ffd9f872f 100644 (file)
@@ -18,6 +18,7 @@
 package org.apache.poi.hssf.usermodel;
 
 import java.text.SimpleDateFormat;
+import java.time.LocalDateTime;
 import java.util.Calendar;
 import java.util.Date;
 import java.util.Iterator;
@@ -47,6 +48,7 @@ import org.apache.poi.ss.usermodel.CellStyle;
 import org.apache.poi.ss.usermodel.CellType;
 import org.apache.poi.ss.usermodel.CellValue;
 import org.apache.poi.ss.usermodel.Comment;
+import org.apache.poi.ss.usermodel.DateUtil;
 import org.apache.poi.ss.usermodel.FormulaError;
 import org.apache.poi.ss.usermodel.Hyperlink;
 import org.apache.poi.ss.usermodel.RichTextString;
@@ -453,11 +455,22 @@ public class HSSFCell extends CellBase {
      * {@inheritDoc}
      *
      * <p>In HSSF, only the number of days is stored. The fractional part is ignored.</p>
-     * @see HSSFDateUtil
+     * @see DateUtil
      * @see org.apache.poi.ss.usermodel.DateUtil
      */
     protected void setCellValueImpl(Date value) {
-        setCellValue(HSSFDateUtil.getExcelDate(value, _book.getWorkbook().isUsing1904DateWindowing()));
+        setCellValue(DateUtil.getExcelDate(value, _book.getWorkbook().isUsing1904DateWindowing()));
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>In HSSF, only the number of days is stored. The fractional part is ignored.</p>
+     * @see DateUtil
+     * @see org.apache.poi.ss.usermodel.DateUtil
+     */
+    protected void setCellValueImpl(LocalDateTime value) {
+        setCellValue(DateUtil.getExcelDate(value, _book.getWorkbook().isUsing1904DateWindowing()));
     }
 
     /**
@@ -465,7 +478,7 @@ public class HSSFCell extends CellBase {
      */
     @Override
     protected void setCellValueImpl(Calendar value) {
-        setCellValue( HSSFDateUtil.getExcelDate(value, _book.getWorkbook().isUsing1904DateWindowing()) );
+        setCellValue( DateUtil.getExcelDate(value, _book.getWorkbook().isUsing1904DateWindowing()) );
     }
 
     /**
@@ -679,9 +692,28 @@ public class HSSFCell extends CellBase {
         }
         double value = getNumericCellValue();
         if (_book.getWorkbook().isUsing1904DateWindowing()) {
-            return HSSFDateUtil.getJavaDate(value, true);
+            return DateUtil.getJavaDate(value, true);
         }
-        return HSSFDateUtil.getJavaDate(value, false);
+        return DateUtil.getJavaDate(value, false);
+    }
+
+    /**
+     * Get the value of the cell as a LocalDateTime.
+     * For strings we throw an exception.
+     * For blank cells we return a null.
+     * See {@link HSSFDataFormatter} for formatting
+     *  this date into a string similar to how excel does.
+     */
+    public LocalDateTime getLocalDateTimeCellValue() {
+
+        if (_cellType == CellType.BLANK) {
+            return null;
+        }
+        double value = getNumericCellValue();
+        if (_book.getWorkbook().isUsing1904DateWindowing()) {
+            return DateUtil.getLocalDateTime(value, true);
+        }
+        return DateUtil.getLocalDateTime(value, false);
     }
 
     /**
@@ -853,7 +885,7 @@ public class HSSFCell extends CellBase {
             default:
                 throw new IllegalStateException("Unexpected formula result type (" + _cellType + ")");
         }
-        
+
     }
 
     /**
@@ -899,7 +931,7 @@ public class HSSFCell extends CellBase {
     /**
      * <p>Set the style for the cell.  The style should be an HSSFCellStyle created/retreived from
      * the HSSFWorkbook.</p>
-     * 
+     *
      * <p>To change the style of a cell without affecting other cells that use the same style,
      * use {@link org.apache.poi.ss.util.CellUtil#setCellStyleProperties(org.apache.poi.ss.usermodel.Cell, java.util.Map)}</p>
      *
@@ -1001,7 +1033,7 @@ public class HSSFCell extends CellBase {
                 return getCellFormula();
             case NUMERIC:
                 //TODO apply the dataformat for this cell
-                if (HSSFDateUtil.isCellDateFormatted(this)) {
+                if (DateUtil.isCellDateFormatted(this)) {
                     SimpleDateFormat sdf = new SimpleDateFormat("dd-MMM-yyyy", LocaleUtil.getUserLocale());
                     sdf.setTimeZone(LocaleUtil.getUserTimeZone());
                     return sdf.format(getDateCellValue());
index eb415269e9e42616fe385d7e920eedf6f23d88aa..f0ff039fd18bdd40db37617767601b3ddf3cc78e 100644 (file)
@@ -30,8 +30,10 @@ import org.apache.poi.ss.usermodel.DateUtil;
 
 /**
  * Contains methods for dealing with Excel dates.
+ * @deprecated Use {@link DateUtil} instead
  */
-public class HSSFDateUtil extends DateUtil {
+@Deprecated
+public final class HSSFDateUtil extends DateUtil {
        protected static int absoluteDay(Calendar cal, boolean use1904windowing) {
                return DateUtil.absoluteDay(cal, use1904windowing);
        }
index d806ef6842637a908df8e4917e44a338d2b576a9..c6a017b26ba7bdc44b0d666b8e29f2372baa1193 100644 (file)
@@ -17,6 +17,8 @@
 
 package org.apache.poi.ss.usermodel;
 
+import java.time.LocalDate;
+import java.time.LocalDateTime;
 import java.util.Calendar;
 import java.util.Date;
 import java.util.Map;
@@ -178,6 +180,42 @@ public interface Cell {
      */
     void setCellValue(Date value);
 
+    /**
+     * <p>Converts the supplied date to its equivalent Excel numeric value and sets
+     * that into the cell.</p>
+     *
+     * <p><b>Note</b> - There is actually no 'DATE' cell type in Excel. In many
+     * cases (when entering date values), Excel automatically adjusts the
+     * <i>cell style</i> to some date format, creating the illusion that the cell
+     * data type is now something besides {@link CellType#NUMERIC}.  POI
+     * does not attempt to replicate this behaviour.  To make a numeric cell
+     * display as a date, use {@link #setCellStyle(CellStyle)} etc.</p>
+     *
+     * @param value the numeric value to set this cell to.  For formulas we'll set the
+     *        precalculated value, for numerics we'll set its value. For other types we
+     *        will change the cell to a numerics cell and set its value.
+     */
+    void setCellValue(LocalDateTime value);
+
+    /**
+     * <p>Converts the supplied date to its equivalent Excel numeric value and sets
+     * that into the cell.</p>
+     *
+     * <p><b>Note</b> - There is actually no 'DATE' cell type in Excel. In many
+     * cases (when entering date values), Excel automatically adjusts the
+     * <i>cell style</i> to some date format, creating the illusion that the cell
+     * data type is now something besides {@link CellType#NUMERIC}.  POI
+     * does not attempt to replicate this behaviour.  To make a numeric cell
+     * display as a date, use {@link #setCellStyle(CellStyle)} etc.</p>
+     *
+     * @param value the numeric value to set this cell to.  For formulas we'll set the
+     *        precalculated value, for numerics we'll set its value. For other types we
+     *        will change the cell to a numerics cell and set its value.
+     */
+    default void setCellValue(LocalDate value) {
+        setCellValue(value.atStartOfDay());
+    }
+
     /**
      * <p>Set a date value for the cell. Excel treats dates as numeric so you will need to format the cell as
      * a date.</p>
@@ -279,6 +317,18 @@ public interface Cell {
      */
     Date getDateCellValue();
 
+    /**
+     * Get the value of the cell as a LocalDateTime.
+     * <p>
+     * For strings we throw an exception. For blank cells we return a null.
+     * </p>
+     * @return the value of the cell as a LocalDateTime
+     * @throws IllegalStateException if the cell type returned by {@link #getCellType()} is {@link CellType#STRING}
+     * @exception NumberFormatException if the cell value isn't a parsable <code>double</code>.
+     * @see DataFormatter for formatting  this date into a string similar to how excel does.
+     */
+    LocalDateTime getLocalDateTimeCellValue();
+
     /**
      * Get the value of the cell as a XSSFRichTextString
      * <p>
index 7193075fa70ff649ac9ff16d46c13571820246a1..819c789ca8b696149e7944bfad434a778d6f2939 100644 (file)
@@ -24,6 +24,7 @@ import org.apache.poi.ss.util.CellRangeAddress;
 import org.apache.poi.ss.util.CellReference;
 import org.apache.poi.util.Removal;
 
+import java.time.LocalDateTime;
 import java.util.Calendar;
 import java.util.Date;
 import java.util.Locale;
@@ -228,6 +229,15 @@ public abstract class CellBase implements Cell {
         setCellValueImpl(value);
     }
 
+    @Override
+    public void setCellValue(LocalDateTime value) {
+        if(value == null) {
+            setBlank();
+            return;
+        }
+        setCellValueImpl(value);
+    }
+
     /**
      * Implementation-specific way to set a date value.
      * <code>value</code> is guaranteed to be non-null.
@@ -237,6 +247,15 @@ public abstract class CellBase implements Cell {
      */
     protected abstract void setCellValueImpl(Date value);
 
+    /**
+     * Implementation-specific way to set a date value.
+     * <code>value</code> is guaranteed to be non-null.
+     * The implementation is expected to adjust the cell type accordingly, so that after this call
+     * getCellType() or getCachedFormulaResultType() would return {@link CellType#NUMERIC}.
+     * @param value the new date to set
+     */
+    protected abstract void setCellValueImpl(LocalDateTime value);
+    
     /**
      * {@inheritDoc}
      */
index fbf34b7ae7483ac74206a2b2071ed9d3425d9cec..981abb4a3176ad8d1ad9f48afead9cfc3a0921cb 100644 (file)
 
 package org.apache.poi.ss.usermodel;
 
+import org.apache.poi.ss.formula.ConditionalFormattingEvaluator;
+import org.apache.poi.util.LocaleUtil;
+
 import java.time.LocalDate;
+import java.time.LocalDateTime;
 import java.time.LocalTime;
 import java.time.ZoneId;
 import java.time.format.DateTimeFormatter;
@@ -31,13 +35,11 @@ import java.util.Date;
 import java.util.TimeZone;
 import java.util.regex.Pattern;
 
-import org.apache.poi.ss.formula.ConditionalFormattingEvaluator;
-import org.apache.poi.util.LocaleUtil;
-
 /**
  * Contains methods for dealing with Excel dates.
  */
 public class DateUtil {
+    // FIXME this should be changed to private and the class marked final once HSSFDateUtil can be removed
     protected DateUtil() {
         // no instances of this class
     }
@@ -74,6 +76,88 @@ public class DateUtil {
             .parseDefaulting(ChronoField.YEAR_OF_ERA, LocaleUtil.getLocaleCalendar().get(Calendar.YEAR))
             .toFormatter();
 
+    /**
+     * Convert a Java Date (at UTC) to LocalDateTime.
+     * @param date the date
+     * @return LocalDateTime instance
+     */
+    public static LocalDateTime toLocalDateTime(Date date) {
+        return date.toInstant()
+                .atZone(TimeZone.getTimeZone("UTC").toZoneId()) // java.util.Date uses UTC
+                .toLocalDateTime();
+    }
+
+    /**
+     * Convert a Java Calendar (at UTC) to LocalDateTime.
+     * @param date the date
+     * @return LocalDateTime instance
+     */
+    public static LocalDateTime toLocalDateTime(Calendar date) {
+        return date.toInstant()
+                .atZone(TimeZone.getTimeZone("UTC").toZoneId()) // java.util.Date uses UTC
+                .toLocalDateTime();
+    }
+
+    /**
+     * Given a LocalDate, converts it into a double representing its internal Excel representation,
+     *   which is the number of days since 1/1/1900. Fractional days represent hours, minutes, and seconds.
+     *
+     * @return Excel representation of Date (-1 if error - test for error by checking for less than 0.1)
+     * @param  date the Date
+     */
+    public static double getExcelDate(LocalDate date) {
+        return getExcelDate(date, false);
+    }
+
+    /**
+     * Given a LocalDate, converts it into a double representing its internal Excel representation,
+     *   which is the number of days since 1/1/1900. Fractional days represent hours, minutes, and seconds.
+     *
+     * @return Excel representation of Date (-1 if error - test for error by checking for less than 0.1)
+     * @param date the Date
+     * @param use1904windowing Should 1900 or 1904 date windowing be used?
+     */
+    public static double getExcelDate(LocalDate date, boolean use1904windowing) {
+        int year = date.getYear();
+        int dayOfYear = date.getDayOfYear();
+        int hour = 0;
+        int minute = 0;
+        int second = 0;
+        int milliSecond = 0;
+
+        return internalGetExcelDate(year, dayOfYear, hour, minute, second, milliSecond, use1904windowing);
+    }
+
+    /**
+     * Given a LocalDateTime, converts it into a double representing its internal Excel representation,
+     *   which is the number of days since 1/1/1900. Fractional days represent hours, minutes, and seconds.
+     *
+     * @return Excel representation of Date (-1 if error - test for error by checking for less than 0.1)
+     * @param  date the Date
+     */
+    public static double getExcelDate(LocalDateTime date) {
+        return getExcelDate(date, false);
+    }
+
+    /**
+     * Given a LocalDateTime, converts it into a double representing its internal Excel representation,
+     *   which is the number of days since 1/1/1900. Fractional days represent hours, minutes, and seconds.
+     *
+     * @return Excel representation of Date (-1 if error - test for error by checking for less than 0.1)
+     * @param date the Date
+     * @param use1904windowing Should 1900 or 1904 date windowing be used?
+     */
+    public static double getExcelDate(LocalDateTime date, boolean use1904windowing) {
+        int year = date.getYear();
+        int dayOfYear = date.getDayOfYear();
+        int hour = date.getHour();
+        int minute = date.getMinute();
+        int second = date.getSecond();
+        int milliSecond = date.getNano()/1_000_000;
+
+        return internalGetExcelDate(year, dayOfYear, hour, minute, second, milliSecond, use1904windowing);
+    }
+
     /**
      * Given a Date, converts it into a double representing its internal Excel representation,
      *   which is the number of days since 1/1/1900. Fractional days represent hours, minutes, and seconds.
@@ -84,6 +168,7 @@ public class DateUtil {
     public static double getExcelDate(Date date) {
         return getExcelDate(date, false);
     }
+    
     /**
      * Given a Date, converts it into a double representing its internal Excel representation,
      *   which is the number of days since 1/1/1900. Fractional days represent hours, minutes, and seconds.
@@ -94,9 +179,17 @@ public class DateUtil {
      */
     public static double getExcelDate(Date date, boolean use1904windowing) {
         Calendar calStart = LocaleUtil.getLocaleCalendar();
-        calStart.setTime(date);   // If date includes hours, minutes, and seconds, set them to 0
-        return internalGetExcelDate(calStart, use1904windowing);
+        calStart.setTime(date);
+        int year = calStart.get(Calendar.YEAR);
+        int dayOfYear = calStart.get(Calendar.DAY_OF_YEAR);
+        int hour = calStart.get(Calendar.HOUR_OF_DAY);
+        int minute = calStart.get(Calendar.MINUTE);
+        int second = calStart.get(Calendar.SECOND);
+        int milliSecond = calStart.get(Calendar.MILLISECOND);
+        
+        return internalGetExcelDate(year, dayOfYear, hour, minute, second, milliSecond, use1904windowing);
     }
+
     /**
      * Given a Date in the form of a Calendar, converts it into a double
      *  representing its internal Excel representation, which is the
@@ -108,15 +201,23 @@ public class DateUtil {
      * @param use1904windowing Should 1900 or 1904 date windowing be used?
      */
     public static double getExcelDate(Calendar date, boolean use1904windowing) {
-        // Don't alter the supplied Calendar as we do our work
-        return internalGetExcelDate( (Calendar)date.clone(), use1904windowing );
-    }
-    private static double internalGetExcelDate(Calendar date, boolean use1904windowing) {
-        if ((!use1904windowing && date.get(Calendar.YEAR) < 1900) ||
-            (use1904windowing && date.get(Calendar.YEAR) < 1904))
+        int year = date.get(Calendar.YEAR);
+        int dayOfYear = date.get(Calendar.DAY_OF_YEAR);
+        int hour = date.get(Calendar.HOUR_OF_DAY);
+        int minute = date.get(Calendar.MINUTE);
+        int second = date.get(Calendar.SECOND);
+        int milliSecond = date.get(Calendar.MILLISECOND);
+
+        return internalGetExcelDate(year, dayOfYear, hour, minute, second, milliSecond, use1904windowing);
+    }
+    
+    private static double internalGetExcelDate(int year, int dayOfYear, int hour, int minute, int second, int milliSecond, boolean use1904windowing) {
+        if ((!use1904windowing && year < 1900) ||
+            (use1904windowing && year < 1904))
         {
             return BAD_DATE;
         }
+
         // Because of daylight time saving we cannot use
         //     date.getTime() - calStart.getTimeInMillis()
         // as the difference in milliseconds between 00:00 and 04:00
@@ -124,14 +225,13 @@ public class DateUtil {
         // be 4 hours.
         // E.g. 2004-03-28 04:00 CEST - 2004-03-28 00:00 CET is 3 hours
         // and 2004-10-31 04:00 CET - 2004-10-31 00:00 CEST is 5 hours
-        double fraction = (((date.get(Calendar.HOUR_OF_DAY) * 60.0
-                             + date.get(Calendar.MINUTE)
-                            ) * 60.0 + date.get(Calendar.SECOND)
-                           ) * 1000.0 + date.get(Calendar.MILLISECOND)
+        double fraction = (((hour * 60.0
+                             + minute
+                            ) * 60.0 + second
+                           ) * 1000.0 + milliSecond
                           ) / DAY_MILLISECONDS;
-        Calendar calStart = dayStart(date);
 
-        double value = fraction + absoluteDay(calStart, use1904windowing);
+        double value = fraction + absoluteDay(year, dayOfYear, use1904windowing);
 
         if (!use1904windowing && value >= 60) {
             value++;
@@ -241,6 +341,86 @@ public class DateUtil {
     public static Date getJavaDate(double date, boolean use1904windowing) {
         return getJavaDate(date, use1904windowing, null, false);
     }
+    
+    /**
+     *  Given an Excel date with using 1900 date windowing, and
+     *   converts it to a java.time.LocalDateTime.
+     *
+     *  NOTE: If the default <code>TimeZone</code> in Java uses Daylight
+     *  Saving Time then the conversion back to an Excel date may not give
+     *  the same value, that is the comparison
+     *  <CODE>excelDate == getExcelDate(getLocalDateTime(excelDate,false))</CODE>
+     *  is not always true. For example if default timezone is
+     *  <code>Europe/Copenhagen</code>, on 2004-03-28 the minute after
+     *  01:59 CET is 03:00 CEST, if the excel date represents a time between
+     *  02:00 and 03:00 then it is converted to past 03:00 summer time
+     *
+     *  @param date  The Excel date.
+     *  @return Java representation of the date, or null if date is not a valid Excel date
+     *  @see java.util.TimeZone
+     */
+    public static LocalDateTime getLocalDateTime(double date) {
+        return getLocalDateTime(date, false, false);
+    }
+
+    /**
+     *  Given an Excel date with either 1900 or 1904 date windowing,
+     *  converts it to a java.time.LocalDateTime.
+     *
+     *  Excel Dates and Times are stored without any timezone
+     *  information. If you know (through other means) that your file
+     *  uses a different TimeZone to the system default, you can use
+     *  this version of the getJavaDate() method to handle it.
+     *
+     *  @param date  The Excel date.
+     *  @param use1904windowing  true if date uses 1904 windowing,
+     *   or false if using 1900 date windowing.
+     *  @return Java representation of the date, or null if date is not a valid Excel date
+     */
+    public static LocalDateTime getLocalDateTime(double date, boolean use1904windowing) {
+        return getLocalDateTime(date, use1904windowing, false);
+    }
+
+    /**
+     *  Given an Excel date with either 1900 or 1904 date windowing,
+     *  converts it to a java.time.LocalDateTime.
+     *
+     *  Excel Dates and Times are stored without any timezone
+     *  information. If you know (through other means) that your file
+     *  uses a different TimeZone to the system default, you can use
+     *  this version of the getJavaDate() method to handle it.
+     *
+     *  @param date  The Excel date.
+     *  @param use1904windowing  true if date uses 1904 windowing,
+     *   or false if using 1900 date windowing.
+     *  @param roundSeconds round to closest second
+     *  @return Java representation of the date, or null if date is not a valid Excel date
+     */
+    public static LocalDateTime getLocalDateTime(double date, boolean use1904windowing, boolean roundSeconds) {
+        if (!isValidExcelDate(date)) {
+            return null;
+        }
+        int wholeDays = (int)Math.floor(date);
+        int millisecondsInDay = (int)((date - wholeDays) * DAY_MILLISECONDS + 0.5);
+        
+        int startYear = 1900;
+        int dayAdjust = -1; // Excel thinks 2/29/1900 is a valid date, which it isn't
+        if (use1904windowing) {
+            startYear = 1904;
+            dayAdjust = 1; // 1904 date windowing uses 1/2/1904 as the first day
+        }
+        else if (wholeDays < 61) {
+            // Date is prior to 3/1/1900, so adjust because Excel thinks 2/29/1900 exists
+            // If Excel date == 2/29/1900, will become 3/1/1900 in Java representation
+            dayAdjust = 0;
+        }
+
+        LocalDateTime ldt = LocalDateTime.of(startYear, 1, 1, 0, 0);
+        ldt = ldt.plusDays(wholeDays+dayAdjust-1);
+        ldt = ldt.plusNanos(millisecondsInDay*1_000_000L);
+
+        return ldt;
+    }
 
     public static void setCalendar(Calendar calendar, int wholeDays,
             int millisecondsInDay, boolean use1904windowing, boolean roundSeconds) {
@@ -616,10 +796,33 @@ public class DateUtil {
      */
     protected static int absoluteDay(Calendar cal, boolean use1904windowing)
     {
-        return cal.get(Calendar.DAY_OF_YEAR)
-               + daysInPriorYears(cal.get(Calendar.YEAR), use1904windowing);
+        return absoluteDay(cal.get(Calendar.YEAR), cal.get(Calendar.DAY_OF_YEAR), use1904windowing);
+    }
+
+    /**
+     * Given a LocalDateTime, return the number of days since 1900/12/31.
+     *
+     * @return days number of days since 1900/12/31
+     * @param  date the Date
+     * @exception IllegalArgumentException if date is invalid
+     */
+    protected static int absoluteDay(LocalDateTime date, boolean use1904windowing)
+    {
+        return absoluteDay(date.getYear(), date.getDayOfYear(), use1904windowing);
     }
 
+    /**
+     * Given a year and day of year, return the number of days since 1900/12/31.
+     *
+     * @return days number of days since 1900/12/31
+     * @param  dayOfYear the day of the year
+     * @param  year the year
+     * @exception IllegalArgumentException if date is invalid
+     */
+    private static int absoluteDay(int year, int dayOfYear, boolean use1904windowing) {
+        return dayOfYear + daysInPriorYears(year, use1904windowing);
+    }
+    
     /**
      * Return the number of days in prior years since 1900
      *
@@ -629,7 +832,7 @@ public class DateUtil {
      * @exception IllegalArgumentException if year is outside of range.
      */
 
-    private static int daysInPriorYears(int yr, boolean use1904windowing)
+    static int daysInPriorYears(int yr, boolean use1904windowing)
     {
         if ((!use1904windowing && yr < 1900) || (use1904windowing && yr < 1904)) {
             throw new IllegalArgumentException("'year' must be 1900 or greater");
index afe9babe5c2cf365d0657e31b0aa4450556e1d3f..31e1c38631500155b0c5aeb9e49a1b0d41a165fc 100644 (file)
@@ -19,6 +19,7 @@ package org.apache.poi.xssf.streaming;
 
 import java.text.DateFormat;
 import java.text.SimpleDateFormat;
+import java.time.LocalDateTime;
 import java.util.Calendar;
 import java.util.Date;
 import java.util.Map;
@@ -185,7 +186,15 @@ public class SXSSFCell extends CellBase {
         setCellValue(DateUtil.getExcelDate(value, date1904));
     }
 
-
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected void setCellValueImpl(LocalDateTime value) {
+        boolean date1904 = getSheet().getWorkbook().isDate1904();
+        setCellValue(DateUtil.getExcelDate(value, date1904));
+    }
+    
     /**
      * {@inheritDoc}
      */
@@ -367,6 +376,27 @@ public class SXSSFCell extends CellBase {
         return DateUtil.getJavaDate(value, date1904);
     }
 
+    /**
+     * Get the value of the cell as a LocalDateTime.
+     * <p>
+     * For strings we throw an exception. For blank cells we return a null.
+     * </p>
+     * @return the value of the cell as a date
+     * @throws IllegalStateException if the cell type returned by {@link #getCellType()} is CellType.STRING
+     * @exception NumberFormatException if the cell value isn't a parsable <code>double</code>.
+     * @see org.apache.poi.ss.usermodel.DataFormatter for formatting  this date into a string similar to how excel does.
+     */
+    @Override
+    public LocalDateTime getLocalDateTimeCellValue() {
+        if (getCellType() == CellType.BLANK) {
+            return null;
+        }
+
+        double value = getNumericCellValue();
+        boolean date1904 = getSheet().getWorkbook().isDate1904();
+        return DateUtil.getLocalDateTime(value, date1904);
+    }
+
     /**
      * Get the value of the cell as a XSSFRichTextString
      * <p>
index 9976527511cfae96e8d561e4326e5ae1acc37265..bbf183c6f55ad6edb2c07cffbfe6f1cc9cc1834c 100644 (file)
@@ -19,6 +19,7 @@ package org.apache.poi.xssf.usermodel;
 
 import java.text.DateFormat;
 import java.text.SimpleDateFormat;
+import java.time.LocalDateTime;
 import java.util.Calendar;
 import java.util.Date;
 
@@ -762,6 +763,27 @@ public final class XSSFCell extends CellBase {
         return DateUtil.getJavaDate(value, date1904);
     }
 
+    /**
+     * Get the value of the cell as a LocalDateTime.
+     * <p>
+     * For strings we throw an exception. For blank cells we return a null.
+     * </p>
+     * @return the value of the cell as a LocalDateTime
+     * @throws IllegalStateException if the cell type returned by {@link #getCellType()} is {@link CellType#STRING}
+     * @exception NumberFormatException if the cell value isn't a parsable <code>double</code>.
+     * @see DataFormatter for formatting  this date into a string similar to how excel does.
+     */
+    @Override
+    public LocalDateTime getLocalDateTimeCellValue() {
+        if (getCellType() == CellType.BLANK) {
+            return null;
+        }
+
+        double value = getNumericCellValue();
+        boolean date1904 = getSheet().getWorkbook().isDate1904();
+        return DateUtil.getLocalDateTime(value, date1904);
+    }
+
     /**
      * {@inheritDoc}
      */
@@ -771,6 +793,15 @@ public final class XSSFCell extends CellBase {
         setCellValue(DateUtil.getExcelDate(value, date1904));
     }
 
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected void setCellValueImpl(LocalDateTime value) {
+        boolean date1904 = getSheet().getWorkbook().isDate1904();
+        setCellValue(DateUtil.getExcelDate(value, date1904));
+    }
+
     /**
      * {@inheritDoc}
      */
index f067725f46044928f6d3465bc22573f2a435c9df..cbeb00223fd1bfcd93848971487a134974e045c0 100644 (file)
@@ -28,6 +28,7 @@ import org.apache.poi.ss.usermodel.BorderStyle;
 import org.apache.poi.ss.usermodel.Cell;
 import org.apache.poi.ss.usermodel.CellStyle;
 import org.apache.poi.ss.usermodel.CellType;
+import org.apache.poi.ss.usermodel.DateUtil;
 import org.apache.poi.ss.usermodel.FillPatternType;
 import org.apache.poi.ss.usermodel.Font;
 import org.apache.poi.ss.usermodel.HorizontalAlignment;
@@ -439,7 +440,7 @@ public final class TestCellStyle extends TestCase {
                             Cell cell = row.getCell(idxCell);
                             cell.getCellStyle().getDataFormatString();
                             if (cell.getCellType() == CellType.NUMERIC) {
-                                boolean isDate = HSSFDateUtil.isCellDateFormatted(cell);
+                                boolean isDate = DateUtil.isCellDateFormatted(cell);
                                 if (idxCell > 0 && isDate) {
                                     fail("cell " + idxCell + " is not a date: " + idxCell);
                                 }
index fc065a6489974e1fad471f32bef8008a8b660205..7cd88fe93bc24308a95a9796b22dbf5cb83f50ea 100644 (file)
@@ -729,7 +729,7 @@ public final class TestFormulas {
         c.setCellStyle(cellStyle);
 
        // assertEquals("Checking hour = " + hour, date.getTime().getTime(),
-       //              HSSFDateUtil.getJavaDate(excelDate).getTime());
+       //              DateUtil.getJavaDate(excelDate).getTime());
 
         for (int k=1; k < 100; k++) {
           r=s.createRow(k);
index 3cbd904988d0e0e7ae45abded4bd4025188aa8f4..066f4bdf666714f23ed2f5f457abb2a98e128395 100644 (file)
 
 package org.apache.poi.hssf.usermodel;
 
-import static java.util.Calendar.AUGUST;
-import static java.util.Calendar.FEBRUARY;
-import static java.util.Calendar.JANUARY;
-import static java.util.Calendar.JULY;
-import static java.util.Calendar.MARCH;
-import static java.util.Calendar.MAY;
-import static java.util.Calendar.OCTOBER;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
 
 import java.io.IOException;
-import java.text.ParseException;
-import java.text.SimpleDateFormat;
-import java.util.Calendar;
-import java.util.Date;
-import java.util.Locale;
 import java.util.TimeZone;
 
 import org.apache.poi.hssf.HSSFTestDataSamples;
@@ -63,289 +50,6 @@ public class TestHSSFDateUtil {
     public static void resetTimeZone() {
         LocaleUtil.setUserTimeZone(userTimeZone);
     }
-    
-    /**
-     * Checks the date conversion functions in the HSSFDateUtil class.
-     */
-    @Test
-    public void dateConversion() {
-
-        // Iteratating over the hours exposes any rounding issues.
-        Calendar cal = LocaleUtil.getLocaleCalendar(2002,JANUARY,1,0,1,1);
-        for (int hour = 0; hour < 23; hour++) {
-            double excelDate = HSSFDateUtil.getExcelDate(cal.getTime(), false);
-
-            assertEquals("Checking hour = " + hour, cal.getTime().getTime(),
-                    HSSFDateUtil.getJavaDate(excelDate, false).getTime());
-
-            cal.add(Calendar.HOUR_OF_DAY, 1);
-        }
-
-        // check 1900 and 1904 date windowing conversions
-        double excelDate = 36526.0;
-        // with 1900 windowing, excelDate is Jan. 1, 2000
-        // with 1904 windowing, excelDate is Jan. 2, 2004
-        cal.set(2000,JANUARY,1,0,0,0); // Jan. 1, 2000
-        Date dateIf1900 = cal.getTime();
-        cal.add(Calendar.YEAR,4); // now Jan. 1, 2004
-        cal.add(Calendar.DATE,1); // now Jan. 2, 2004
-        Date dateIf1904 = cal.getTime();
-        // 1900 windowing
-        assertEquals("Checking 1900 Date Windowing",
-                dateIf1900.getTime(),
-                HSSFDateUtil.getJavaDate(excelDate,false).getTime());
-        // 1904 windowing
-        assertEquals("Checking 1904 Date Windowing",
-                dateIf1904.getTime(),
-                HSSFDateUtil.getJavaDate(excelDate,true).getTime());
-    }
-
-    /**
-     * Checks the conversion of a java.util.date to Excel on a day when
-     * Daylight Saving Time starts.
-     */
-    @Test
-    public void excelConversionOnDSTStart() {
-        Calendar cal = LocaleUtil.getLocaleCalendar(2004,MARCH,28,0,0,0);
-        for (int hour = 0; hour < 24; hour++) {
-            
-            // Skip 02:00 CET as that is the Daylight change time
-            // and Java converts it automatically to 03:00 CEST
-            if (hour == 2) {
-                continue;
-            }
-
-            cal.set(Calendar.HOUR_OF_DAY, hour);
-            Date javaDate = cal.getTime();
-
-            
-            double excelDate = HSSFDateUtil.getExcelDate(javaDate, false);
-            double difference = excelDate - Math.floor(excelDate);
-            int differenceInHours = (int) (difference * 24 * 60 + 0.5) / 60;
-            assertEquals("Checking " + hour + " hour on Daylight Saving Time start date",
-                    hour,
-                    differenceInHours);
-            assertEquals("Checking " + hour + " hour on Daylight Saving Time start date",
-                    javaDate.getTime(),
-                    HSSFDateUtil.getJavaDate(excelDate, false).getTime());
-        }
-    }
-
-    /**
-     * Checks the conversion of an Excel date to a java.util.date on a day when
-     * Daylight Saving Time starts.
-     */
-    @Test
-    public void javaConversionOnDSTStart() {
-        Calendar cal = LocaleUtil.getLocaleCalendar(2004,MARCH,28,0,0,0);
-        double excelDate = HSSFDateUtil.getExcelDate(cal.getTime(), false);
-        double oneHour = 1.0 / 24;
-        double oneMinute = oneHour / 60;
-        for (int hour = 0; hour < 24; hour++, excelDate += oneHour) {
-
-            // Skip 02:00 CET as that is the Daylight change time
-            // and Java converts it automatically to 03:00 CEST
-            if (hour == 2) {
-                continue;
-            }
-
-            cal.set(Calendar.HOUR_OF_DAY, hour);
-            Date javaDate = HSSFDateUtil.getJavaDate(excelDate, false);
-            double actDate = HSSFDateUtil.getExcelDate(javaDate, false);
-            assertEquals("Checking " + hour + " hours on Daylight Saving Time start date",
-                    excelDate, actDate, oneMinute);
-        }
-    }
-
-    /**
-     * Checks the conversion of a java.util.Date to Excel on a day when
-     * Daylight Saving Time ends.
-     */
-    @Test
-    public void excelConversionOnDSTEnd() {
-        Calendar cal = LocaleUtil.getLocaleCalendar(2004,OCTOBER,31,0,0,0);
-        for (int hour = 0; hour < 24; hour++) {
-            cal.set(Calendar.HOUR_OF_DAY, hour);
-            Date javaDate = cal.getTime();
-            double excelDate = HSSFDateUtil.getExcelDate(javaDate, false);
-            double difference = excelDate - Math.floor(excelDate);
-            int differenceInHours = (int) (difference * 24 * 60 + 0.5) / 60;
-            assertEquals("Checking " + hour + " hour on Daylight Saving Time end date",
-                    hour,
-                    differenceInHours);
-            assertEquals("Checking " + hour + " hour on Daylight Saving Time start date",
-                    javaDate.getTime(),
-                    HSSFDateUtil.getJavaDate(excelDate, false).getTime());
-        }
-    }
-
-    /**
-     * Checks the conversion of an Excel date to java.util.Date on a day when
-     * Daylight Saving Time ends.
-     */
-    @Test
-    public void javaConversionOnDSTEnd() {
-        Calendar cal = LocaleUtil.getLocaleCalendar(2004,OCTOBER,31,0,0,0);
-        double excelDate = HSSFDateUtil.getExcelDate(cal.getTime(), false);
-        double oneHour = 1.0 / 24;
-        double oneMinute = oneHour / 60;
-        for (int hour = 0; hour < 24; hour++, excelDate += oneHour) {
-            cal.set(Calendar.HOUR_OF_DAY, hour);
-            Date javaDate = HSSFDateUtil.getJavaDate(excelDate, false);
-            assertEquals("Checking " + hour + " hours on Daylight Saving Time start date",
-                    excelDate,
-                    HSSFDateUtil.getExcelDate(javaDate, false), oneMinute);
-        }
-    }
-
-    /**
-     * Tests that we deal with time-zones properly
-     */
-    @Test
-    public void calendarConversion() {
-        TimeZone userTZ = LocaleUtil.getUserTimeZone();
-        LocaleUtil.setUserTimeZone(TimeZone.getTimeZone("CET"));
-        try {
-            Calendar cal = LocaleUtil.getLocaleCalendar(2002,JANUARY,1,12,1,1);
-            Date expected = cal.getTime();
-    
-            // Iterating over the hours exposes any rounding issues.
-            for (int hour = -12; hour <= 12; hour++)
-            {
-                String id = "GMT" + (hour < 0 ? "" : "+") + hour + ":00";
-                cal.setTimeZone(TimeZone.getTimeZone(id));
-                cal.set(Calendar.HOUR_OF_DAY, 12);
-                double excelDate = HSSFDateUtil.getExcelDate(cal, false);
-                Date javaDate = HSSFDateUtil.getJavaDate(excelDate);
-    
-                // Should match despite time-zone
-                assertEquals("Checking timezone " + id, expected.getTime(), javaDate.getTime());
-            }
-            
-            // Check that the timezone aware getter works correctly 
-            TimeZone cet = TimeZone.getTimeZone("Europe/Copenhagen");
-            TimeZone ldn = TimeZone.getTimeZone("Europe/London");
-            
-            // 12:45 on 27th April 2012
-            double excelDate = 41026.53125;
-            
-            // Same, no change
-            assertEquals(
-                  HSSFDateUtil.getJavaDate(excelDate, false).getTime(),
-                  HSSFDateUtil.getJavaDate(excelDate, false, cet).getTime()
-            );
-            
-            // London vs Copenhagen, should differ by an hour
-            Date cetDate = HSSFDateUtil.getJavaDate(excelDate, false);
-            Date ldnDate = HSSFDateUtil.getJavaDate(excelDate, false, ldn);
-            assertEquals(ldnDate.getTime() - cetDate.getTime(), 60*60*1000);
-        } finally {
-            LocaleUtil.setUserTimeZone(userTZ);
-        }
-    }
-
-    /**
-     * Tests that we correctly detect date formats as such
-     */
-    @Test
-    public void identifyDateFormats() {
-        // First up, try with a few built in date formats
-        short[] builtins = new short[] { 0x0e, 0x0f, 0x10, 0x16, 0x2d, 0x2e };
-        for (short builtin : builtins) {
-            String formatStr = HSSFDataFormat.getBuiltinFormat(builtin);
-            assertTrue( HSSFDateUtil.isInternalDateFormat(builtin) );
-            assertTrue( HSSFDateUtil.isADateFormat(builtin,formatStr) );
-        }
-
-        // Now try a few built-in non date formats
-        builtins = new short[] { 0x01, 0x02, 0x17, 0x1f, 0x30 };
-        for (short builtin : builtins) {
-            String formatStr = HSSFDataFormat.getBuiltinFormat(builtin);
-            assertFalse( HSSFDateUtil.isInternalDateFormat(builtin) );
-            assertFalse( HSSFDateUtil.isADateFormat(builtin,formatStr) );
-        }
-
-        // Now for some non-internal ones
-        // These come after the real ones
-        int numBuiltins = HSSFDataFormat.getNumberOfBuiltinBuiltinFormats();
-        assertTrue(numBuiltins < 60);
-        short formatId = 60;
-        assertFalse( HSSFDateUtil.isInternalDateFormat(formatId) );
-
-        // Valid ones first
-        String[] formats = new String[] {
-                "yyyy-mm-dd", "yyyy/mm/dd", "yy/mm/dd", "yy/mmm/dd",
-                "dd/mm/yy", "dd/mm/yyyy", "dd/mmm/yy",
-                "dd-mm-yy", "dd-mm-yyyy",
-                "DD-MM-YY", "DD-mm-YYYY",
-                "dd\\-mm\\-yy", // Sometimes escaped
-                "dd.mm.yyyy", "dd\\.mm\\.yyyy",
-                "dd\\ mm\\.yyyy AM", "dd\\ mm\\.yyyy pm",
-                 "dd\\ mm\\.yyyy\\-dd", "[h]:mm:ss",
-                 "mm/dd/yy", "\"mm\"/\"dd\"/\"yy\"",
-                 "m\\/d\\/yyyy", 
-
-                // These crazy ones are valid
-                "yyyy-mm-dd;@", "yyyy/mm/dd;@",
-                "dd-mm-yy;@", "dd-mm-yyyy;@",
-                // These even crazier ones are also valid
-                // (who knows what they mean though...)
-                "[$-F800]dddd\\,\\ mmm\\ dd\\,\\ yyyy",
-                "[$-F900]ddd/mm/yyy",
-                // These ones specify colours, who knew that was allowed?
-                "[BLACK]dddd/mm/yy",
-                "[yeLLow]yyyy-mm-dd"
-        };
-        for (String format : formats) {
-            assertTrue(
-                    format + " is a date format",
-                    HSSFDateUtil.isADateFormat(formatId, format)
-            );
-        }
-
-        // Then time based ones too
-        formats = new String[] {
-                "yyyy-mm-dd hh:mm:ss", "yyyy/mm/dd HH:MM:SS",
-                "mm/dd HH:MM", "yy/mmm/dd SS",
-                "mm/dd HH:MM AM", "mm/dd HH:MM am",
-                "mm/dd HH:MM PM", "mm/dd HH:MM pm",
-                "m/d/yy h:mm AM/PM",
-                "hh:mm:ss", "hh:mm:ss.0", "mm:ss.0",
-                //support elapsed time [h],[m],[s]
-                "[hh]", "[mm]", "[ss]", "[SS]", "[red][hh]"
-        };
-        for (String format : formats) {
-            assertTrue(
-                    format + " is a datetime format",
-                    HSSFDateUtil.isADateFormat(formatId, format)
-            );
-        }
-
-        // Then invalid ones
-        formats = new String[] {
-                "yyyy*mm*dd",
-                "0.0", "0.000",
-                "0%", "0.0%",
-                "[]Foo", "[BLACK]0.00%",
-                "[ms]", "[Mh]",
-                "", null
-        };
-        for (String format : formats) {
-            assertFalse(
-                    format + " is not a date or datetime format",
-                    HSSFDateUtil.isADateFormat(formatId, format)
-            );
-        }
-
-        // And these are ones we probably shouldn't allow,
-        //  but would need a better regexp
-        formats = new String[] {
-                "yyyy:mm:dd",
-        };
-        for (String format : formats) {
-        //    assertFalse( HSSFDateUtil.isADateFormat(formatId, formats[i]) );
-        }
-    }
 
     /**
      * Test that against a real, test file, we still do everything
@@ -374,217 +78,42 @@ public class TestHSSFDateUtil {
         style = cell.getCellStyle();
         assertEquals(aug_10_2007, cell.getNumericCellValue(), 0.0001);
         assertEquals("d-mmm-yy", style.getDataFormatString());
-        assertTrue(HSSFDateUtil.isInternalDateFormat(style.getDataFormat()));
-        assertTrue(HSSFDateUtil.isADateFormat(style.getDataFormat(), style.getDataFormatString()));
-        assertTrue(HSSFDateUtil.isCellDateFormatted(cell));
+        assertTrue(DateUtil.isInternalDateFormat(style.getDataFormat()));
+        assertTrue(DateUtil.isADateFormat(style.getDataFormat(), style.getDataFormatString()));
+        assertTrue(DateUtil.isCellDateFormatted(cell));
 
         row  = sheet.getRow(1);
         cell = row.getCell(1);
         style = cell.getCellStyle();
         assertEquals(aug_10_2007, cell.getNumericCellValue(), 0.0001);
-        assertFalse(HSSFDateUtil.isInternalDateFormat(cell.getCellStyle().getDataFormat()));
-        assertTrue(HSSFDateUtil.isADateFormat(style.getDataFormat(), style.getDataFormatString()));
-        assertTrue(HSSFDateUtil.isCellDateFormatted(cell));
+        assertFalse(DateUtil.isInternalDateFormat(cell.getCellStyle().getDataFormat()));
+        assertTrue(DateUtil.isADateFormat(style.getDataFormat(), style.getDataFormatString()));
+        assertTrue(DateUtil.isCellDateFormatted(cell));
 
         row  = sheet.getRow(2);
         cell = row.getCell(1);
         style = cell.getCellStyle();
         assertEquals(aug_10_2007, cell.getNumericCellValue(), 0.0001);
-        assertTrue(HSSFDateUtil.isInternalDateFormat(cell.getCellStyle().getDataFormat()));
-        assertTrue(HSSFDateUtil.isADateFormat(style.getDataFormat(), style.getDataFormatString()));
-        assertTrue(HSSFDateUtil.isCellDateFormatted(cell));
+        assertTrue(DateUtil.isInternalDateFormat(cell.getCellStyle().getDataFormat()));
+        assertTrue(DateUtil.isADateFormat(style.getDataFormat(), style.getDataFormatString()));
+        assertTrue(DateUtil.isCellDateFormatted(cell));
 
         row  = sheet.getRow(3);
         cell = row.getCell(1);
         style = cell.getCellStyle();
         assertEquals(aug_10_2007, cell.getNumericCellValue(), 0.0001);
-        assertFalse(HSSFDateUtil.isInternalDateFormat(cell.getCellStyle().getDataFormat()));
-        assertTrue(HSSFDateUtil.isADateFormat(style.getDataFormat(), style.getDataFormatString()));
-        assertTrue(HSSFDateUtil.isCellDateFormatted(cell));
+        assertFalse(DateUtil.isInternalDateFormat(cell.getCellStyle().getDataFormat()));
+        assertTrue(DateUtil.isADateFormat(style.getDataFormat(), style.getDataFormatString()));
+        assertTrue(DateUtil.isCellDateFormatted(cell));
 
         row  = sheet.getRow(4);
         cell = row.getCell(1);
         style = cell.getCellStyle();
         assertEquals(aug_10_2007, cell.getNumericCellValue(), 0.0001);
-        assertFalse(HSSFDateUtil.isInternalDateFormat(cell.getCellStyle().getDataFormat()));
-        assertTrue(HSSFDateUtil.isADateFormat(style.getDataFormat(), style.getDataFormatString()));
-        assertTrue(HSSFDateUtil.isCellDateFormatted(cell));
-        
-        workbook.close();
-    }
-
-    @Test
-    public void excelDateBorderCases() throws ParseException {
-        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd", Locale.ROOT);
-        df.setTimeZone(LocaleUtil.getUserTimeZone());
-        
-        assertEquals(1.0, DateUtil.getExcelDate(df.parse("1900-01-01")), 0.00001);
-        assertEquals(31.0, DateUtil.getExcelDate(df.parse("1900-01-31")), 0.00001);
-        assertEquals(32.0, DateUtil.getExcelDate(df.parse("1900-02-01")), 0.00001);
-        assertEquals(/* BAD_DATE! */ -1.0, DateUtil.getExcelDate(df.parse("1899-12-31")), 0.00001);
-    }
-
-    @Test
-    public void dateBug_2Excel() {
-        assertEquals(59.0, HSSFDateUtil.getExcelDate(createDate(1900, FEBRUARY, 28), false), 0.00001);
-        assertEquals(61.0, HSSFDateUtil.getExcelDate(createDate(1900, MARCH, 1), false), 0.00001);
-
-        assertEquals(37315.00, HSSFDateUtil.getExcelDate(createDate(2002, FEBRUARY, 28), false), 0.00001);
-        assertEquals(37316.00, HSSFDateUtil.getExcelDate(createDate(2002, MARCH, 1), false), 0.00001);
-        assertEquals(37257.00, HSSFDateUtil.getExcelDate(createDate(2002, JANUARY, 1), false), 0.00001);
-        assertEquals(38074.00, HSSFDateUtil.getExcelDate(createDate(2004, MARCH, 28), false), 0.00001);
-    }
-
-    @Test
-    public void dateBug_2Java() {
-        assertEquals(createDate(1900, FEBRUARY, 28), HSSFDateUtil.getJavaDate(59.0, false));
-        assertEquals(createDate(1900, MARCH, 1), HSSFDateUtil.getJavaDate(61.0, false));
-
-        assertEquals(createDate(2002, FEBRUARY, 28), HSSFDateUtil.getJavaDate(37315.00, false));
-        assertEquals(createDate(2002, MARCH, 1), HSSFDateUtil.getJavaDate(37316.00, false));
-        assertEquals(createDate(2002, JANUARY, 1), HSSFDateUtil.getJavaDate(37257.00, false));
-        assertEquals(createDate(2004, MARCH, 28), HSSFDateUtil.getJavaDate(38074.00, false));
-    }
-
-    @Test
-    public void date1904() {
-        assertEquals(createDate(1904, JANUARY, 2), HSSFDateUtil.getJavaDate(1.0, true));
-        assertEquals(createDate(1904, JANUARY, 1), HSSFDateUtil.getJavaDate(0.0, true));
-        assertEquals(0.0, HSSFDateUtil.getExcelDate(createDate(1904, JANUARY, 1), true), 0.00001);
-        assertEquals(1.0, HSSFDateUtil.getExcelDate(createDate(1904, JANUARY, 2), true), 0.00001);
-
-        assertEquals(createDate(1998, JULY, 5), HSSFDateUtil.getJavaDate(35981, false));
-        assertEquals(createDate(1998, JULY, 5), HSSFDateUtil.getJavaDate(34519, true));
-
-        assertEquals(35981.0, HSSFDateUtil.getExcelDate(createDate(1998, JULY, 5), false), 0.00001);
-        assertEquals(34519.0, HSSFDateUtil.getExcelDate(createDate(1998, JULY, 5), true), 0.00001);
-    }
-
-    /**
-     * @param month zero based
-     * @param day one based
-     */
-    private static Date createDate(int year, int month, int day) {
-        return createDate(year, month, day, 0, 0, 0);
-    }
-
-    /**
-     * @param month zero based
-     * @param day one based
-     */
-    private static Date createDate(int year, int month, int day, int hour, int minute, int second) {
-        Calendar c = LocaleUtil.getLocaleCalendar(year, month, day, hour, minute, second);
-        return c.getTime();
-    }
-
-    /**
-     * Check if HSSFDateUtil.getAbsoluteDay works as advertised.
-     */
-    @Test
-    public void absoluteDay() {
-        // 1 Jan 1900 is 1 day after 31 Dec 1899
-        Calendar cal = LocaleUtil.getLocaleCalendar(1900,JANUARY,1,0,0,0);
-        assertEquals("Checking absolute day (1 Jan 1900)", 1, HSSFDateUtil.absoluteDay(cal, false));
-        // 1 Jan 1901 is 366 days after 31 Dec 1899
-        cal.set(1901,JANUARY,1,0,0,0);
-        assertEquals("Checking absolute day (1 Jan 1901)", 366, HSSFDateUtil.absoluteDay(cal, false));
-    }
-
-    @Test
-    public void absoluteDayYearTooLow() {
-        Calendar cal = LocaleUtil.getLocaleCalendar(1899,JANUARY,1,0,0,0);
-        try {
-               HSSFDateUtil.absoluteDay(cal, false);
-               fail("Should fail here");
-        } catch (IllegalArgumentException e) {
-               // expected here
-        }
-
-        try {
-            cal.set(1903,JANUARY,1,0,0,0);
-            HSSFDateUtil.absoluteDay(cal, true);
-               fail("Should fail here");
-        } catch (IllegalArgumentException e) {
-               // expected here
-        }
-    }
+        assertFalse(DateUtil.isInternalDateFormat(cell.getCellStyle().getDataFormat()));
+        assertTrue(DateUtil.isADateFormat(style.getDataFormat(), style.getDataFormatString()));
+        assertTrue(DateUtil.isCellDateFormatted(cell));
 
-    @Test
-    public void convertTime() {
-
-        final double delta = 1E-7; // a couple of digits more accuracy than strictly required
-        assertEquals(0.5, HSSFDateUtil.convertTime("12:00"), delta);
-        assertEquals(2.0/3, HSSFDateUtil.convertTime("16:00"), delta);
-        assertEquals(0.0000116, HSSFDateUtil.convertTime("0:00:01"), delta);
-        assertEquals(0.7330440, HSSFDateUtil.convertTime("17:35:35"), delta);
-    }
-
-    @Test
-    public void parseDate() {
-        assertEquals(createDate(2008, AUGUST, 3), HSSFDateUtil.parseYYYYMMDDDate("2008/08/03"));
-        assertEquals(createDate(1994, MAY, 1), HSSFDateUtil.parseYYYYMMDDDate("1994/05/01"));
-    }
-
-    /**
-     * Ensure that date values *with* a fractional portion get the right time of day
-     */
-    @Test
-    public void convertDateTime() {
-       // Excel day 30000 is date 18-Feb-1982
-        // 0.7 corresponds to time 16:48:00
-        Date actual = HSSFDateUtil.getJavaDate(30000.7);
-        Date expected = createDate(1982, 1, 18, 16, 48, 0);
-        assertEquals(expected, actual);
-    }
-
-    /**
-     * User reported a datetime issue in POI-2.5:
-     *  Setting Cell's value to Jan 1, 1900 without a time doesn't return the same value set to
-     * @throws IOException 
-     */
-    @Test
-    public void bug19172() throws IOException
-    {
-        HSSFWorkbook workbook = new HSSFWorkbook();
-        HSSFSheet sheet = workbook.createSheet();
-        HSSFCell cell = sheet.createRow(0).createCell(0);
-
-        // A pseudo special Excel dates
-        Calendar cal = LocaleUtil.getLocaleCalendar(1900, JANUARY, 1);
-
-        Date valueToTest = cal.getTime();
-
-        cell.setCellValue(valueToTest);
-
-        Date returnedValue = cell.getDateCellValue();
-
-        assertEquals(valueToTest.getTime(), returnedValue.getTime());
-        
         workbook.close();
     }
-
-    /**
-     * DateUtil.isCellFormatted(Cell) should not true for a numeric cell 
-     * that's formatted as ".0000"
-     */
-    @Test
-    public void bug54557() throws Exception {
-       final String format = ".0000";
-       boolean isDateFormat = HSSFDateUtil.isADateFormat(165, format);
-       
-       assertEquals(false, isDateFormat);
-    }
-    
-    @Test
-    public void bug56269() throws Exception {
-        double excelFraction = 41642.45833321759d;
-        Calendar calNoRound = HSSFDateUtil.getJavaCalendar(excelFraction, false);
-        assertEquals(10, calNoRound.get(Calendar.HOUR));
-        assertEquals(59, calNoRound.get(Calendar.MINUTE));
-        assertEquals(59, calNoRound.get(Calendar.SECOND));
-        Calendar calRound = HSSFDateUtil.getJavaCalendar(excelFraction, false, null, true);
-        assertEquals(11, calRound.get(Calendar.HOUR));
-        assertEquals(0, calRound.get(Calendar.MINUTE));
-        assertEquals(0, calRound.get(Calendar.SECOND));
-    }
 }
index 57be4a6d6d4e8a60b18dbde9c9069cb961cb4fb9..e7fe40c02f8ee508073ed2b34e8b962f35594c04 100644 (file)
@@ -29,6 +29,7 @@ import org.apache.poi.hssf.model.InternalSheet;
 import org.apache.poi.hssf.record.BOFRecord;
 import org.apache.poi.hssf.record.EOFRecord;
 import org.apache.poi.hssf.record.RecordBase;
+import org.apache.poi.ss.usermodel.DateUtil;
 import org.apache.poi.util.LocaleUtil;
 import org.junit.Test;
 
@@ -49,7 +50,7 @@ public final class TestReadWriteChart {
 
         //System.out.println("first assertion for date");
         Calendar calExp = LocaleUtil.getLocaleCalendar(2000, 0, 1, 10, 51, 2);
-        Date dateAct = HSSFDateUtil.getJavaDate(firstCell.getNumericCellValue(), false);
+        Date dateAct = DateUtil.getJavaDate(firstCell.getNumericCellValue(), false);
         assertEquals(calExp.getTime(), dateAct);
         HSSFRow  row  = sheet.createRow(15);
         HSSFCell cell = row.createCell(1);
index a20050f7ee5deedcf4f02015738e3ce5513c5442..42675eea2f8eb075875e619c8e6dd7222b22c292 100644 (file)
@@ -26,13 +26,13 @@ import java.util.Iterator;
 
 import org.apache.poi.hssf.HSSFTestDataSamples;
 import org.apache.poi.hssf.usermodel.HSSFCell;
-import org.apache.poi.hssf.usermodel.HSSFDateUtil;
 import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator;
 import org.apache.poi.hssf.usermodel.HSSFRow;
 import org.apache.poi.hssf.usermodel.HSSFSheet;
 import org.apache.poi.hssf.usermodel.HSSFWorkbook;
 import org.apache.poi.ss.formula.eval.EvaluationException;
 import org.apache.poi.ss.usermodel.CellType;
+import org.apache.poi.ss.usermodel.DateUtil;
 import org.apache.poi.ss.usermodel.Row;
 import org.apache.poi.util.LocaleUtil;
 import org.junit.Test;
@@ -97,7 +97,7 @@ public final class TestYearFracCalculatorFromSpreadsheet {
                int month = getIntCell(row, yearColumn + 1);
                int day = getIntCell(row, yearColumn + 2);
                Calendar c = LocaleUtil.getLocaleCalendar(year, month-1, day);
-               return HSSFDateUtil.getExcelDate(c.getTime());
+               return DateUtil.getExcelDate(c.getTime());
        }
 
        private static int getIntCell(HSSFRow row, int colIx) {
index 159477057ede3ade58eddf343bb1eff2ab604503..a20f444d70b89c7be865b8ce38cb8109af731df0 100644 (file)
@@ -24,10 +24,10 @@ import java.util.Calendar;
 import java.util.Date;
 import java.util.Locale;
 
-import org.apache.poi.hssf.usermodel.HSSFDateUtil;
 import org.apache.poi.ss.formula.eval.BoolEval;
 import org.apache.poi.ss.formula.eval.NumberEval;
 import org.apache.poi.ss.formula.eval.ValueEval;
+import org.apache.poi.ss.usermodel.DateUtil;
 import org.apache.poi.util.LocaleUtil;
 import org.junit.Test;
 
@@ -160,6 +160,6 @@ public final class TestDays360 {
     }
     
     private static NumberEval convert(Date d) {
-        return new NumberEval(HSSFDateUtil.getExcelDate(d));
+        return new NumberEval(DateUtil.getExcelDate(d));
     }
 }
index e2c195fa413ecf0887db8d0e512d67b6fe720476..0d784a6b3f50f47fa00a93eba722b3bb6d51c51c 100644 (file)
@@ -30,6 +30,8 @@ import static org.mockito.Mockito.verify;
 
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
 import java.util.ArrayList;
 import java.util.Calendar;
 import java.util.Date;
@@ -113,6 +115,26 @@ public abstract class BaseTestCell {
         assertProhibitedValueAccess(cell, CellType.BOOLEAN, CellType.STRING,
                 CellType.FORMULA, CellType.ERROR);
 
+        cell.setCellErrorValue(FormulaError.NA.getCode());
+        assertEquals(FormulaError.NA.getCode(), cell.getErrorCellValue());
+        assertEquals(CellType.ERROR, cell.getCellType());
+        assertProhibitedValueAccess(cell, CellType.NUMERIC, CellType.BOOLEAN,
+                CellType.FORMULA, CellType.STRING);
+
+        LocalDateTime ldt = DateUtil.toLocalDateTime(c);
+        cell.setCellValue(ldt);
+        assertEquals(ldt, cell.getLocalDateTimeCellValue());
+        assertEquals(CellType.NUMERIC, cell.getCellType());
+        assertProhibitedValueAccess(cell, CellType.BOOLEAN, CellType.STRING,
+                CellType.FORMULA, CellType.ERROR);
+
+        LocalDate ld = ldt.toLocalDate();
+        cell.setCellValue(ld);
+        assertEquals(ld, cell.getLocalDateTimeCellValue().toLocalDate());
+        assertEquals(CellType.NUMERIC, cell.getCellType());
+        assertProhibitedValueAccess(cell, CellType.BOOLEAN, CellType.STRING,
+                CellType.FORMULA, CellType.ERROR);
+
         cell.setCellErrorValue(FormulaError.NA.getCode());
         assertEquals(FormulaError.NA.getCode(), cell.getErrorCellValue());
         assertEquals(CellType.ERROR, cell.getCellType());
index c721774461b2bcbbc49daaebc3b080bcbe09db23..65b19656699620a0ca20c7183de4b021b6407840 100644 (file)
 
 package org.apache.poi.ss.usermodel;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
+import static java.util.Calendar.*;
+import static org.junit.Assert.*;
+import static org.junit.Assert.fail;
 
+import java.io.IOException;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
 import java.util.Calendar;
 import java.util.Date;
+import java.util.Locale;
 import java.util.TimeZone;
 
+import org.apache.poi.hssf.HSSFTestDataSamples;
+import org.apache.poi.hssf.model.InternalWorkbook;
+import org.apache.poi.hssf.usermodel.*;
 import org.apache.poi.util.LocaleUtil;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
 import org.junit.Test;
 
 public class TestDateUtil {
 
+    static TimeZone userTimeZone;
+
+    @BeforeClass
+    public static void setCEST() {
+        userTimeZone = LocaleUtil.getUserTimeZone();
+        LocaleUtil.setUserTimeZone(TimeZone.getTimeZone("CEST"));
+    }
+
+    @AfterClass
+    public static void resetTimeZone() {
+        LocaleUtil.setUserTimeZone(userTimeZone);
+    }
+
     @Test
     public void getJavaDate_InvalidValue() {
         double dateValue = -1;
@@ -93,6 +118,32 @@ public class TestDateUtil {
         assertEquals(expCal, actCal[2]);
         assertEquals(expCal, actCal[3]);
     }
+
+    @Test
+    public void getLocalDateTime_InvalidValue() {
+        double dateValue = -1;
+        TimeZone tz = LocaleUtil.getUserTimeZone();
+        boolean use1904windowing = false;
+        boolean roundSeconds = false;
+
+        assertEquals(null, DateUtil.getLocalDateTime(dateValue));
+        assertEquals(null, DateUtil.getLocalDateTime(dateValue, use1904windowing));
+        assertEquals(null, DateUtil.getLocalDateTime(dateValue, use1904windowing, roundSeconds));
+    }
+
+    @Test
+    public void getLocalDateTime_ValidValue() {
+        double dateValue = 0;
+        boolean use1904windowing = false;
+        boolean roundSeconds = false;
+        
+        // note that the Date and Calendar examples use a zero day of month which is invalid in LocalDateTime 
+        LocalDateTime date = LocalDateTime.of(1899, 12, 31, 0, 0);
+
+        assertEquals(date, DateUtil.getLocalDateTime(dateValue));
+        assertEquals(date, DateUtil.getLocalDateTime(dateValue, use1904windowing));
+        assertEquals(date, DateUtil.getLocalDateTime(dateValue, use1904windowing, roundSeconds));
+    }
     
     @Test
     public void isADateFormat() {
@@ -123,4 +174,571 @@ public class TestDateUtil {
         // Cell show "2016年12月8日"
         assertTrue(DateUtil.isADateFormat(178, "[DBNum3][$-804]yyyy\"\u5e74\"m\"\u6708\"d\"\u65e5\";@"));
     }
+    /**
+     * Checks the date conversion functions in the DateUtil class.
+     */
+    @Test
+    public void dateConversion() {
+
+        // Iteratating over the hours exposes any rounding issues.
+        Calendar cal = LocaleUtil.getLocaleCalendar(2002,JANUARY,1,0,1,1);
+        for (int hour = 0; hour < 24; hour++) {
+            double excelDate = DateUtil.getExcelDate(cal.getTime(), false);
+
+            assertEquals("getJavaDate: Checking hour = " + hour, cal.getTime().getTime(),
+                    DateUtil.getJavaDate(excelDate, false).getTime());
+
+            LocalDateTime ldt = LocalDateTime.ofInstant(cal.toInstant(), cal.getTimeZone().toZoneId());
+            assertEquals("getLocalDateTime: Checking hour = " + hour, ldt,
+                    DateUtil.getLocalDateTime(excelDate, false));
+
+            cal.add(Calendar.HOUR_OF_DAY, 1);
+        }
+
+        // check 1900 and 1904 date windowing conversions
+        double excelDate = 36526.0;
+        // with 1900 windowing, excelDate is Jan. 1, 2000
+        // with 1904 windowing, excelDate is Jan. 2, 2004
+        cal.set(2000,JANUARY,1,0,0,0); // Jan. 1, 2000
+        Date dateIf1900 = cal.getTime();
+        cal.add(Calendar.YEAR,4); // now Jan. 1, 2004
+        cal.add(Calendar.DATE,1); // now Jan. 2, 2004
+        Date dateIf1904 = cal.getTime();
+        // 1900 windowing
+        assertEquals("Checking 1900 Date Windowing",
+                dateIf1900.getTime(),
+                DateUtil.getJavaDate(excelDate,false).getTime());
+        // 1904 windowing
+        assertEquals("Checking 1904 Date Windowing",
+                dateIf1904.getTime(),
+                DateUtil.getJavaDate(excelDate,true).getTime());
+        // 1900 windowing (LocalDateTime)
+        assertEquals("Checking 1900 Date Windowing",
+                LocalDateTime.of(2000,1,1,0,0),
+                DateUtil.getLocalDateTime(excelDate,false));
+        // 1904 windowing (LocalDateTime)
+        assertEquals("Checking 1904 Date Windowing",
+                LocalDateTime.of(2004,1,2,0,0),
+                DateUtil.getLocalDateTime(excelDate,true));
+    }
+
+    /**
+     * Checks the conversion of a java.util.date to Excel on a day when
+     * Daylight Saving Time starts.
+     */
+    @Test
+    public void excelConversionOnDSTStart() {
+        Calendar cal = LocaleUtil.getLocaleCalendar(2004,MARCH,28,0,0,0);
+        for (int hour = 0; hour < 24; hour++) {
+
+            // Skip 02:00 CET as that is the Daylight change time
+            // and Java converts it automatically to 03:00 CEST
+            if (hour == 2) {
+                continue;
+            }
+
+            cal.set(Calendar.HOUR_OF_DAY, hour);
+            Date javaDate = cal.getTime();
+            double excelDate = DateUtil.getExcelDate(javaDate, false);
+            double difference = excelDate - Math.floor(excelDate);
+            int differenceInHours = (int) (difference * 24 * 60 + 0.5) / 60;
+
+            assertEquals("Checking " + hour + " hour on Daylight Saving Time start date",
+                    hour,
+                    differenceInHours);
+            assertEquals("Checking " + hour + " hour on Daylight Saving Time start date",
+                    javaDate.getTime(),
+                    DateUtil.getJavaDate(excelDate, false).getTime());
+            
+            // perform the same checks with LocalDateTime
+            LocalDateTime localDate = LocalDateTime.of(2004,3,28,hour,0,0);
+            double excelLocalDate = DateUtil.getExcelDate(localDate, false);
+            double differenceLocalDate = excelLocalDate - Math.floor(excelLocalDate);
+            int differenceLocalDateInHours = (int) (differenceLocalDate * 24 * 60 + 0.5) / 60;
+
+            assertEquals("Checking " + hour + " hour on Daylight Saving Time start date (LocalDateTime)",
+                    hour,
+                    differenceLocalDateInHours);
+            assertEquals("Checking " + hour + " hour on Daylight Saving Time start date (LocalDateTime)",
+                    localDate,
+                    DateUtil.getLocalDateTime(excelLocalDate, false));
+        }
+    }
+
+    /**
+     * Checks the conversion of an Excel date to a java.util.date on a day when
+     * Daylight Saving Time starts.
+     */
+    @Test
+    public void javaConversionOnDSTStart() {
+        Calendar cal = LocaleUtil.getLocaleCalendar(2004,MARCH,28,0,0,0);
+        double excelDate = DateUtil.getExcelDate(cal.getTime(), false);
+        double oneHour = 1.0 / 24;
+        double oneMinute = oneHour / 60;
+        for (int hour = 0; hour < 24; hour++, excelDate += oneHour) {
+
+            // Skip 02:00 CET as that is the Daylight change time
+            // and Java converts it automatically to 03:00 CEST
+            if (hour == 2) {
+                continue;
+            }
+
+            cal.set(Calendar.HOUR_OF_DAY, hour);
+            Date javaDate = DateUtil.getJavaDate(excelDate, false);
+            double actDate = DateUtil.getExcelDate(javaDate, false);
+            assertEquals("Checking " + hour + " hours on Daylight Saving Time start date",
+                    excelDate, actDate, oneMinute);
+            
+            // perform the same check with LocalDateTime
+            cal.set(Calendar.HOUR_OF_DAY, hour);
+            LocalDateTime localDate = DateUtil.getLocalDateTime(excelDate, false);
+            double actLocalDate = DateUtil.getExcelDate(localDate, false);
+            assertEquals("Checking " + hour + " hours on Daylight Saving Time start date (LocalDateTime)",
+                    excelDate, actLocalDate, oneMinute);
+        }
+    }
+
+    /**
+     * Checks the conversion of a java.util.Date to Excel on a day when
+     * Daylight Saving Time ends.
+     */
+    @Test
+    public void excelConversionOnDSTEnd() {
+        Calendar cal = LocaleUtil.getLocaleCalendar(2004,OCTOBER,31,0,0,0);
+        for (int hour = 0; hour < 24; hour++) {
+            cal.set(Calendar.HOUR_OF_DAY, hour);
+            Date javaDate = cal.getTime();
+            double excelDate = DateUtil.getExcelDate(javaDate, false);
+            double difference = excelDate - Math.floor(excelDate);
+            int differenceInHours = (int) (difference * 24 * 60 + 0.5) / 60;
+            assertEquals("Checking " + hour + " hour on Daylight Saving Time end date",
+                    hour,
+                    differenceInHours);
+            assertEquals("Checking " + hour + " hour on Daylight Saving Time start date",
+                    javaDate.getTime(),
+                    DateUtil.getJavaDate(excelDate, false).getTime());
+            
+            // perform the same checks using LocalDateTime
+            LocalDateTime localDate = LocalDateTime.of(2004,10,31,hour,0,0);
+            double excelLocalDate = DateUtil.getExcelDate(localDate, false);
+            double differenceLocalDate = excelLocalDate - Math.floor(excelLocalDate);
+            int differenceLocalDateInHours = (int) (difference * 24 * 60 + 0.5) / 60;
+            assertEquals("Checking " + hour + " hour on Daylight Saving Time end date (LocalDateTime)",
+                    hour,
+                    differenceLocalDateInHours);
+            assertEquals("Checking " + hour + " hour on Daylight Saving Time start date (LocalDateTime)",
+                    localDate,
+                    DateUtil.getLocalDateTime(excelLocalDate, false));
+        }
+    }
+
+    /**
+     * Checks the conversion of an Excel date to java.util.Date on a day when
+     * Daylight Saving Time ends.
+     */
+    @Test
+    public void javaConversionOnDSTEnd() {
+        Calendar cal = LocaleUtil.getLocaleCalendar(2004,OCTOBER,31,0,0,0);
+        double excelDate = DateUtil.getExcelDate(cal.getTime(), false);
+        double oneHour = 1.0 / 24;
+        double oneMinute = oneHour / 60;
+        for (int hour = 0; hour < 24; hour++, excelDate += oneHour) {
+            cal.set(Calendar.HOUR_OF_DAY, hour);
+            Date javaDate = DateUtil.getJavaDate(excelDate, false);
+            assertEquals("Checking " + hour + " hours on Daylight Saving Time start date",
+                    excelDate,
+                    DateUtil.getExcelDate(javaDate, false), oneMinute);
+
+            // perform the same checks using LocalDateTime
+            LocalDateTime localDate = DateUtil.getLocalDateTime(excelDate, false);
+            assertEquals("Checking " + hour + " hours on Daylight Saving Time start date",
+                    excelDate,
+                    DateUtil.getExcelDate(localDate, false), oneMinute);
+        }
+    }
+
+    /**
+     * Tests that we deal with time-zones properly
+     */
+    @Test
+    public void calendarConversion() {
+        TimeZone userTZ = LocaleUtil.getUserTimeZone();
+        LocaleUtil.setUserTimeZone(TimeZone.getTimeZone("CET"));
+        try {
+            Calendar cal = LocaleUtil.getLocaleCalendar(2002,JANUARY,1,12,1,1);
+            Date expected = cal.getTime();
+
+            // Iterating over the hours exposes any rounding issues.
+            for (int hour = -12; hour <= 12; hour++)
+            {
+                String id = "GMT" + (hour < 0 ? "" : "+") + hour + ":00";
+                cal.setTimeZone(TimeZone.getTimeZone(id));
+                cal.set(Calendar.HOUR_OF_DAY, 12);
+                double excelDate = DateUtil.getExcelDate(cal, false);
+                Date javaDate = DateUtil.getJavaDate(excelDate);
+
+                // Should match despite time-zone
+                assertEquals("Checking timezone " + id, expected.getTime(), javaDate.getTime());
+            }
+
+            // Check that the timezone aware getter works correctly
+            TimeZone cet = TimeZone.getTimeZone("Europe/Copenhagen");
+            TimeZone ldn = TimeZone.getTimeZone("Europe/London");
+
+            // 12:45 on 27th April 2012
+            double excelDate = 41026.53125;
+
+            // Same, no change
+            assertEquals(
+                    DateUtil.getJavaDate(excelDate, false).getTime(),
+                    DateUtil.getJavaDate(excelDate, false, cet).getTime()
+            );
+
+            // London vs Copenhagen, should differ by an hour
+            Date cetDate = DateUtil.getJavaDate(excelDate, false);
+            Date ldnDate = DateUtil.getJavaDate(excelDate, false, ldn);
+            assertEquals(ldnDate.getTime() - cetDate.getTime(), 60*60*1000);
+        } finally {
+            LocaleUtil.setUserTimeZone(userTZ);
+        }
+    }
+
+    /**
+     * Tests that we correctly detect date formats as such
+     */
+    @Test
+    public void identifyDateFormats() {
+        // First up, try with a few built in date formats
+        short[] builtins = new short[] { 0x0e, 0x0f, 0x10, 0x16, 0x2d, 0x2e };
+        for (short builtin : builtins) {
+            String formatStr = HSSFDataFormat.getBuiltinFormat(builtin);
+            assertTrue( DateUtil.isInternalDateFormat(builtin) );
+            assertTrue( DateUtil.isADateFormat(builtin,formatStr) );
+        }
+
+        // Now try a few built-in non date formats
+        builtins = new short[] { 0x01, 0x02, 0x17, 0x1f, 0x30 };
+        for (short builtin : builtins) {
+            String formatStr = HSSFDataFormat.getBuiltinFormat(builtin);
+            assertFalse( DateUtil.isInternalDateFormat(builtin) );
+            assertFalse( DateUtil.isADateFormat(builtin,formatStr) );
+        }
+
+        // Now for some non-internal ones
+        // These come after the real ones
+        int numBuiltins = HSSFDataFormat.getNumberOfBuiltinBuiltinFormats();
+        assertTrue(numBuiltins < 60);
+        short formatId = 60;
+        assertFalse( DateUtil.isInternalDateFormat(formatId) );
+
+        // Valid ones first
+        String[] formats = new String[] {
+                "yyyy-mm-dd", "yyyy/mm/dd", "yy/mm/dd", "yy/mmm/dd",
+                "dd/mm/yy", "dd/mm/yyyy", "dd/mmm/yy",
+                "dd-mm-yy", "dd-mm-yyyy",
+                "DD-MM-YY", "DD-mm-YYYY",
+                "dd\\-mm\\-yy", // Sometimes escaped
+                "dd.mm.yyyy", "dd\\.mm\\.yyyy",
+                "dd\\ mm\\.yyyy AM", "dd\\ mm\\.yyyy pm",
+                "dd\\ mm\\.yyyy\\-dd", "[h]:mm:ss",
+                "mm/dd/yy", "\"mm\"/\"dd\"/\"yy\"",
+                "m\\/d\\/yyyy",
+
+                // These crazy ones are valid
+                "yyyy-mm-dd;@", "yyyy/mm/dd;@",
+                "dd-mm-yy;@", "dd-mm-yyyy;@",
+                // These even crazier ones are also valid
+                // (who knows what they mean though...)
+                "[$-F800]dddd\\,\\ mmm\\ dd\\,\\ yyyy",
+                "[$-F900]ddd/mm/yyy",
+                // These ones specify colours, who knew that was allowed?
+                "[BLACK]dddd/mm/yy",
+                "[yeLLow]yyyy-mm-dd"
+        };
+        for (String format : formats) {
+            assertTrue(
+                    format + " is a date format",
+                    DateUtil.isADateFormat(formatId, format)
+            );
+        }
+
+        // Then time based ones too
+        formats = new String[] {
+                "yyyy-mm-dd hh:mm:ss", "yyyy/mm/dd HH:MM:SS",
+                "mm/dd HH:MM", "yy/mmm/dd SS",
+                "mm/dd HH:MM AM", "mm/dd HH:MM am",
+                "mm/dd HH:MM PM", "mm/dd HH:MM pm",
+                "m/d/yy h:mm AM/PM",
+                "hh:mm:ss", "hh:mm:ss.0", "mm:ss.0",
+                //support elapsed time [h],[m],[s]
+                "[hh]", "[mm]", "[ss]", "[SS]", "[red][hh]"
+        };
+        for (String format : formats) {
+            assertTrue(
+                    format + " is a datetime format",
+                    DateUtil.isADateFormat(formatId, format)
+            );
+        }
+
+        // Then invalid ones
+        formats = new String[] {
+                "yyyy*mm*dd",
+                "0.0", "0.000",
+                "0%", "0.0%",
+                "[]Foo", "[BLACK]0.00%",
+                "[ms]", "[Mh]",
+                "", null
+        };
+        for (String format : formats) {
+            assertFalse(
+                    format + " is not a date or datetime format",
+                    DateUtil.isADateFormat(formatId, format)
+            );
+        }
+
+        // And these are ones we probably shouldn't allow,
+        //  but would need a better regexp
+        formats = new String[] {
+                "yyyy:mm:dd",
+        };
+        for (String format : formats) {
+            //    assertFalse( DateUtil.isADateFormat(formatId, formats[i]) );
+        }
+    }
+
+    @Test
+    public void excelDateBorderCases() throws ParseException {
+        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd", Locale.ROOT);
+        df.setTimeZone(LocaleUtil.getUserTimeZone());
+
+        Date date1 = df.parse("1900-01-01");
+        assertEquals(1.0, DateUtil.getExcelDate(date1), 0.00001);
+        assertEquals(1.0, DateUtil.getExcelDate(DateUtil.toLocalDateTime(date1)), 0.00001);
+        Date date31 = df.parse("1900-01-31");
+        assertEquals(31.0, DateUtil.getExcelDate(date31), 0.00001);
+        assertEquals(31.0, DateUtil.getExcelDate(DateUtil.toLocalDateTime(date31)), 0.00001);
+        Date date32 = df.parse("1900-02-01");
+        assertEquals(32.0, DateUtil.getExcelDate(date32), 0.00001);
+        assertEquals(32.0, DateUtil.getExcelDate(DateUtil.toLocalDateTime(date32)), 0.00001);
+        Date dateMinus1 = df.parse("1899-12-31");
+        assertEquals(/* BAD_DATE! */ -1.0, DateUtil.getExcelDate(dateMinus1), 0.00001);
+        assertEquals(/* BAD_DATE! */ -1.0, DateUtil.getExcelDate(DateUtil.toLocalDateTime(dateMinus1)), 0.00001);
+    }
+
+    @Test
+    public void dateBug_2Excel() {
+        assertEquals(59.0, DateUtil.getExcelDate(createDate(1900, FEBRUARY, 28), false), 0.00001);
+        assertEquals(61.0, DateUtil.getExcelDate(createDate(1900, MARCH, 1), false), 0.00001);
+
+        assertEquals(37315.00, DateUtil.getExcelDate(createDate(2002, FEBRUARY, 28), false), 0.00001);
+        assertEquals(37316.00, DateUtil.getExcelDate(createDate(2002, MARCH, 1), false), 0.00001);
+        assertEquals(37257.00, DateUtil.getExcelDate(createDate(2002, JANUARY, 1), false), 0.00001);
+        assertEquals(38074.00, DateUtil.getExcelDate(createDate(2004, MARCH, 28), false), 0.00001);
+
+        // perform the same checks using LocalDateTime
+        assertEquals(59.0, DateUtil.getExcelDate(LocalDateTime.of(1900, 2, 28, 0,0), false), 0.00001);
+        assertEquals(61.0, DateUtil.getExcelDate(LocalDateTime.of(1900, 3, 1, 0,0), false), 0.00001);
+
+        assertEquals(37315.00, DateUtil.getExcelDate(LocalDateTime.of(2002, 2, 28, 0,0), false), 0.00001);
+        assertEquals(37316.00, DateUtil.getExcelDate(LocalDateTime.of(2002, 3, 1, 0,0), false), 0.00001);
+        assertEquals(37257.00, DateUtil.getExcelDate(LocalDateTime.of(2002, 1, 1, 0,0), false), 0.00001);
+        assertEquals(38074.00, DateUtil.getExcelDate(LocalDateTime.of(2004, 3, 28, 0,0), false), 0.00001);
+    }
+
+    @Test
+    public void dateBug_2Java() {
+        assertEquals(createDate(1900, FEBRUARY, 28), DateUtil.getJavaDate(59.0, false));
+        assertEquals(createDate(1900, MARCH, 1), DateUtil.getJavaDate(61.0, false));
+
+        assertEquals(createDate(2002, FEBRUARY, 28), DateUtil.getJavaDate(37315.00, false));
+        assertEquals(createDate(2002, MARCH, 1), DateUtil.getJavaDate(37316.00, false));
+        assertEquals(createDate(2002, JANUARY, 1), DateUtil.getJavaDate(37257.00, false));
+        assertEquals(createDate(2004, MARCH, 28), DateUtil.getJavaDate(38074.00, false));
+
+        // perform the same checks using LocalDateTime
+        assertEquals(LocalDateTime.of(1900, 2, 28, 0, 0), DateUtil.getLocalDateTime(59.0, false));
+        assertEquals(LocalDateTime.of(1900, 3, 1, 0, 0), DateUtil.getLocalDateTime(61.0, false));
+
+        assertEquals(LocalDateTime.of(2002, 2, 28, 0, 0), DateUtil.getLocalDateTime(37315.00, false));
+        assertEquals(LocalDateTime.of(2002, 3, 1, 0, 0), DateUtil.getLocalDateTime(37316.00, false));
+        assertEquals(LocalDateTime.of(2002, 1, 1, 0, 0), DateUtil.getLocalDateTime(37257.00, false));
+        assertEquals(LocalDateTime.of(2004, 3, 28, 0, 0), DateUtil.getLocalDateTime(38074.00, false));
+    }
+
+    @Test
+    public void date1904() {
+        assertEquals(createDate(1904, JANUARY, 2), DateUtil.getJavaDate(1.0, true));
+        assertEquals(createDate(1904, JANUARY, 1), DateUtil.getJavaDate(0.0, true));
+        assertEquals(0.0, DateUtil.getExcelDate(createDate(1904, JANUARY, 1), true), 0.00001);
+        assertEquals(1.0, DateUtil.getExcelDate(createDate(1904, JANUARY, 2), true), 0.00001);
+
+        assertEquals(createDate(1998, JULY, 5), DateUtil.getJavaDate(35981, false));
+        assertEquals(createDate(1998, JULY, 5), DateUtil.getJavaDate(34519, true));
+
+        assertEquals(35981.0, DateUtil.getExcelDate(createDate(1998, JULY, 5), false), 0.00001);
+        assertEquals(34519.0, DateUtil.getExcelDate(createDate(1998, JULY, 5), true), 0.00001);
+
+        // perform the same checks using LocalDateTime
+        assertEquals(LocalDateTime.of(1904, 1, 2, 0, 0), DateUtil.getLocalDateTime(1.0, true));
+        assertEquals(LocalDateTime.of(1904, 1, 1, 0, 0), DateUtil.getLocalDateTime(0.0, true));
+        assertEquals(0.0, DateUtil.getExcelDate(LocalDateTime.of(1904, 1, 1, 0, 0), true), 0.00001);
+        assertEquals(1.0, DateUtil.getExcelDate(LocalDateTime.of(1904, 1, 2, 0, 0), true), 0.00001);
+
+        assertEquals(LocalDateTime.of(1998, 7, 5, 0, 0), DateUtil.getLocalDateTime(35981, false));
+        assertEquals(LocalDateTime.of(1998, 7, 5, 0, 0), DateUtil.getLocalDateTime(34519, true));
+
+        assertEquals(35981.0, DateUtil.getExcelDate(LocalDateTime.of(1998, 7, 5, 0, 0), false), 0.00001);
+        assertEquals(34519.0, DateUtil.getExcelDate(LocalDateTime.of(1998, 7, 5, 0, 0), true), 0.00001);
+    }
+
+    /**
+     * @param month zero based
+     * @param day one based
+     */
+    private static Date createDate(int year, int month, int day) {
+        return createDate(year, month, day, 0, 0, 0);
+    }
+
+    /**
+     * @param month zero based
+     * @param day one based
+     */
+    private static Date createDate(int year, int month, int day, int hour, int minute, int second) {
+        Calendar c = LocaleUtil.getLocaleCalendar(year, month, day, hour, minute, second);
+        return c.getTime();
+    }
+
+    /**
+     * Check if DateUtil.getAbsoluteDay works as advertised.
+     */
+    @Test
+    public void absoluteDay() {
+        // 1 Jan 1900 is 1 day after 31 Dec 1899
+        Calendar cal = LocaleUtil.getLocaleCalendar(1900,JANUARY,1,0,0,0);
+        assertEquals("Checking absolute day (1 Jan 1900)", 1, DateUtil.absoluteDay(cal, false));
+        LocalDateTime ldt = LocalDateTime.of(1900,1,1,0,0,0);
+        assertEquals("Checking absolute day (1 Jan 1900) (LocalDateTime)", 1, DateUtil.absoluteDay(ldt, false));
+        // 1 Jan 1901 is 366 days after 31 Dec 1899
+        ldt = LocalDateTime.of(1901,1,1,0,0,0);
+        cal.set(1901,JANUARY,1,0,0,0);
+        assertEquals("Checking absolute day (1 Jan 1901) (LocalDateTime)", 366, DateUtil.absoluteDay(ldt, false));
+    }
+
+    @Test
+    public void absoluteDayYearTooLow() {
+        Calendar cal = LocaleUtil.getLocaleCalendar(1899,JANUARY,1,0,0,0);
+        try {
+            DateUtil.absoluteDay(cal, false);
+            fail("Should fail here");
+        } catch (IllegalArgumentException e) {
+            // expected here
+        }
+
+        try {
+            cal.set(1903,JANUARY,1,0,0,0);
+            DateUtil.absoluteDay(cal, true);
+            fail("Should fail here");
+        } catch (IllegalArgumentException e) {
+            // expected here
+        }
+        
+        // same for LocalDateTime
+        try {
+            DateUtil.absoluteDay(LocalDateTime.of(1899,1,1,0,0,0), false);
+            fail("Should fail here");
+        } catch (IllegalArgumentException e) {
+            // expected here
+        }
+
+        try {
+            DateUtil.absoluteDay(LocalDateTime.of(1903,1,1,0,0,0), true);
+            fail("Should fail here");
+        } catch (IllegalArgumentException e) {
+            // expected here
+        }
+    }
+
+    @Test
+    public void convertTime() {
+
+        final double delta = 1E-7; // a couple of digits more accuracy than strictly required
+        assertEquals(0.5, DateUtil.convertTime("12:00"), delta);
+        assertEquals(2.0/3, DateUtil.convertTime("16:00"), delta);
+        assertEquals(0.0000116, DateUtil.convertTime("0:00:01"), delta);
+        assertEquals(0.7330440, DateUtil.convertTime("17:35:35"), delta);
+    }
+
+    @Test
+    public void parseDate() {
+        assertEquals(createDate(2008, AUGUST, 3), DateUtil.parseYYYYMMDDDate("2008/08/03"));
+        assertEquals(createDate(1994, MAY, 1), DateUtil.parseYYYYMMDDDate("1994/05/01"));
+    }
+
+    /**
+     * Ensure that date values *with* a fractional portion get the right time of day
+     */
+    @Test
+    public void convertDateTime() {
+        // Excel day 30000 is date 18-Feb-1982
+        // 0.7 corresponds to time 16:48:00
+        Date actual = DateUtil.getJavaDate(30000.7);
+        Date expected = createDate(1982, 1, 18, 16, 48, 0);
+        assertEquals(expected, actual);
+
+        // note that months in Calendar are zero-based, in LocalDateTime one-based
+        LocalDateTime actualLocalDate = DateUtil.getLocalDateTime(30000.7);
+        LocalDateTime expectedLocalDate = LocalDateTime.of(1982, 2, 18, 16, 48, 0);
+        assertEquals(expectedLocalDate, actualLocalDate);
+    }
+
+    /**
+     * User reported a datetime issue in POI-2.5:
+     *  Setting Cell's value to Jan 1, 1900 without a time doesn't return the same value set to
+     * @throws IOException
+     */
+    @Test
+    public void bug19172() throws IOException
+    {
+        HSSFWorkbook workbook = new HSSFWorkbook();
+        HSSFSheet sheet = workbook.createSheet();
+        HSSFCell cell = sheet.createRow(0).createCell(0);
+
+        // A pseudo special Excel dates
+        Calendar cal = LocaleUtil.getLocaleCalendar(1900, JANUARY, 1);
+
+        Date valueToTest = cal.getTime();
+
+        cell.setCellValue(valueToTest);
+
+        Date returnedValue = cell.getDateCellValue();
+
+        assertEquals(valueToTest.getTime(), returnedValue.getTime());
+
+        workbook.close();
+    }
+
+    /**
+     * DateUtil.isCellFormatted(Cell) should not true for a numeric cell
+     * that's formatted as ".0000"
+     */
+    @Test
+    public void bug54557() throws Exception {
+        final String format = ".0000";
+        boolean isDateFormat = DateUtil.isADateFormat(165, format);
+
+        assertEquals(false, isDateFormat);
+    }
+
+    @Test
+    public void bug56269() throws Exception {
+        double excelFraction = 41642.45833321759d;
+        Calendar calNoRound = DateUtil.getJavaCalendar(excelFraction, false);
+        assertEquals(10, calNoRound.get(Calendar.HOUR));
+        assertEquals(59, calNoRound.get(Calendar.MINUTE));
+        assertEquals(59, calNoRound.get(Calendar.SECOND));
+        Calendar calRound = DateUtil.getJavaCalendar(excelFraction, false, null, true);
+        assertEquals(11, calRound.get(Calendar.HOUR));
+        assertEquals(0, calRound.get(Calendar.MINUTE));
+        assertEquals(0, calRound.get(Calendar.SECOND));
+    }
 }