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;
}
// ---------------------------------------------------------------------------------------------------------
}
Related examples in the same category