Mega Code Archive

 
Categories / Java / Development Class
 

Dealing with Excel dates

/**  *   * LibFormula : a free Java formula library  *   *  * Project Info:  http://reporting.pentaho.org/libformula/  *  * (C) Copyright 2006-2007, by Pentaho Corporation and Contributors.  *  * This library is free software; you can redistribute it and/or modify it under the terms  * of the GNU Lesser General Public License as published by the Free Software Foundation;  * either version 2.1 of the License, or (at your option) any later version.  *  * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;  * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  * See the GNU Lesser General Public License for more details.  *  * You should have received a copy of the GNU Lesser General Public License along with this  * library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,  * Boston, MA 02111-1307, USA.  *  * [Java is a trademark or registered trademark of Sun Microsystems, Inc.  * in the United States and other countries.]  *  *  * ------------  * $Id: HSSFDateUtil.java 3522 2007-10-16 10:56:57Z tmorgner $  * ------------  * (C) Copyright 2006-2007, by Pentaho Corporation.  */ /*  * DateUtil.java  *  * Created on January 19, 2002, 9:30 AM  */ import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; /**  * Contains methods for dealing with Excel dates. <br/> Modified by Cedric  * Pronzato  *   * @author Michael Harhen  * @author Glen Stampoultzis (glens at apache.org)  * @author Dan Sherman (dsherman at isisph.com)  * @author Hack Kampbjorn (hak at 2mba.dk)  */ public class HSSFDateUtil {   private HSSFDateUtil() {   }   private static final int BAD_DATE = -1; // used to specify that date is                                           // invalid   private static final long DAY_MILLISECONDS = 24 * 60 * 60 * 1000;   private static final double CAL_1900_ABSOLUTE = (double) absoluteDay(new GregorianCalendar(1900,       Calendar.JANUARY, 1)) - 2.0;   /**    * 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.    *     * @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(final Date date) {     Calendar calStart = new GregorianCalendar();     calStart.setTime(date); // If date includes hours, minutes, and seconds, set                             // them to 0     // if (calStart.get(Calendar.YEAR) < 1900)     // {     // return BAD_DATE;     // }     // else     // {     // Because of daylight time saving we cannot use     // date.getTime() - calStart.getTimeInMillis()     // as the difference in milliseconds between 00:00 and 04:00     // can be 3, 4 or 5 hours but Excel expects it to always     // 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     final double fraction = (((calStart.get(Calendar.HOUR_OF_DAY) * 60 + calStart         .get(Calendar.MINUTE)) * 60 + calStart.get(Calendar.SECOND)) * 1000 + calStart         .get(Calendar.MILLISECOND))         / (double) DAY_MILLISECONDS;     calStart = dayStart(calStart);     return fraction + (double) absoluteDay(calStart) - CAL_1900_ABSOLUTE;   }   // }   /**    * Given a excel date, converts it into a Date. Assumes 1900 date windowing.    *     * @param date    *          the Excel Date    *     * @return Java representation of a date (null if error)    * @see #getJavaDate(double,boolean)    */   public static Date getJavaDate(final double date) {     return getJavaDate(date, true);   }   /**    * Given an Excel date with either 1900 or 1904 date windowing, converts it to    * a java.util.Date.    *     * 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(getJavaDate(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.    * @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    * @see java.util.TimeZone    */   public static Date getJavaDate(final double date, final boolean use1904windowing) {     if (isValidExcelDate(date)) {       int startYear = 1900;       int dayAdjust = -1; // Excel thinks 2/29/1900 is a valid date, which it                           // isn't       final int wholeDays = (int) Math.floor(date);       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;       }       final GregorianCalendar calendar = new GregorianCalendar(startYear, 0, wholeDays + dayAdjust);       final int millisecondsInDay = (int) ((date - Math.floor(date)) * (double) DAY_MILLISECONDS + 0.5);       calendar.set(GregorianCalendar.MILLISECOND, millisecondsInDay);       return calendar.getTime();     } else {       return null;     }   }   /**    * given a format ID this will check whether the format represents an internal    * date format or not.    */   public static boolean isInternalDateFormat(final int format) {     boolean retval;     switch (format) {     // Internal Date Formats as described on page 427 in     // Microsoft Excel Dev's Kit...     case 0x0e:     case 0x0f:     case 0x10:     case 0x11:     case 0x12:     case 0x13:     case 0x14:     case 0x15:     case 0x16:     case 0x2d:     case 0x2e:     case 0x2f:       retval = true;       break;     default:       retval = false;       break;     }     return retval;   }   /**    * Given a double, checks if it is a valid Excel date.    *     * @return true if valid    * @param value    *          the double value    */   public static boolean isValidExcelDate(final double value) {     return (value > -Double.MIN_VALUE);   }   /**    * Given a Calendar, return the number of days since 1600/12/31.    *     * @return days number of days since 1600/12/31    * @param cal    *          the Calendar    * @exception IllegalArgumentException    *              if date is invalid    */   private static int absoluteDay(final Calendar cal) {     return cal.get(Calendar.DAY_OF_YEAR) + daysInPriorYears(cal.get(Calendar.YEAR));   }   /**    * Return the number of days in prior years since 1601    *     * @return days number of days in years prior to yr.    * @param yr    *          a year (1600 < yr < 4000)    * @exception IllegalArgumentException    *              if year is outside of range.    */   private static int daysInPriorYears(final int yr) {     if (yr < 1601) {       throw new IllegalArgumentException("'year' must be 1601 or greater");     }     final int y = yr - 1601;     return 365 * y // days in prior years         + y / 4 // plus julian leap days in prior years         - y / 100 // minus prior century years         + y / 400;   }   // set HH:MM:SS fields of cal to 00:00:00:000   private static Calendar dayStart(final Calendar cal) {     cal.get(Calendar.HOUR_OF_DAY); // force recalculation of internal fields     cal.set(Calendar.HOUR_OF_DAY, 0);     cal.set(Calendar.MINUTE, 0);     cal.set(Calendar.SECOND, 0);     cal.set(Calendar.MILLISECOND, 0);     cal.get(Calendar.HOUR_OF_DAY); // force recalculation of internal fields     return cal;   }   // --------------------------------------------------------------------------------------------------------- }