Source for org.jfree.formula.util.HSSFDateUtil

   1: /**
   2:  * =========================================
   3:  * LibFormula : a free Java formula library
   4:  * =========================================
   5:  *
   6:  * Project Info:  http://reporting.pentaho.org/libformula/
   7:  *
   8:  * (C) Copyright 2006-2007, by Pentaho Corporation and Contributors.
   9:  *
  10:  * This library is free software; you can redistribute it and/or modify it under the terms
  11:  * of the GNU Lesser General Public License as published by the Free Software Foundation;
  12:  * either version 2.1 of the License, or (at your option) any later version.
  13:  *
  14:  * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
  15:  * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  16:  * See the GNU Lesser General Public License for more details.
  17:  *
  18:  * You should have received a copy of the GNU Lesser General Public License along with this
  19:  * library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
  20:  * Boston, MA 02111-1307, USA.
  21:  *
  22:  * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
  23:  * in the United States and other countries.]
  24:  *
  25:  *
  26:  * ------------
  27:  * $Id: HSSFDateUtil.java 3522 2007-10-16 10:56:57Z tmorgner $
  28:  * ------------
  29:  * (C) Copyright 2006-2007, by Pentaho Corporation.
  30:  */
  31: 
  32: /*
  33:  * DateUtil.java
  34:  *
  35:  * Created on January 19, 2002, 9:30 AM
  36:  */
  37: package org.jfree.formula.util;
  38: 
  39: import java.util.Calendar;
  40: import java.util.Date;
  41: import java.util.GregorianCalendar;
  42: 
  43: import org.jfree.formula.LibFormulaBoot;
  44: 
  45: /**
  46:  * Contains methods for dealing with Excel dates.
  47:  * <br/>
  48:  * Modified by Cedric Pronzato
  49:  *
  50:  * @author  Michael Harhen
  51:  * @author  Glen Stampoultzis (glens at apache.org)
  52:  * @author  Dan Sherman (dsherman at isisph.com)
  53:  * @author  Hack Kampbjorn (hak at 2mba.dk)
  54:  */
  55: 
  56: public class HSSFDateUtil
  57: {
  58:     private HSSFDateUtil()
  59:     {
  60:     }
  61: 
  62:     private static final int    BAD_DATE          =
  63:         -1;   // used to specify that date is invalid
  64:     private static final long   DAY_MILLISECONDS  = 24 * 60 * 60 * 1000;
  65:     private static final double CAL_1900_ABSOLUTE =
  66:         ( double ) absoluteDay(new GregorianCalendar(1900, Calendar
  67:         .JANUARY, 1)) - 2.0;
  68: 
  69:     /**
  70:      * Given a Date, converts it into a double representing its internal Excel representation,
  71:      *   which is the number of days since 1/1/1900. Fractional days represent hours, minutes, and seconds.
  72:      *
  73:      * @return Excel representation of Date (-1 if error - test for error by checking for less than 0.1)
  74:      * @param  date the Date
  75:      */
  76: 
  77:     public static double getExcelDate(final Date date)
  78:     {
  79:         Calendar calStart = new GregorianCalendar();
  80: 
  81:         calStart.setTime(
  82:             date);   // If date includes hours, minutes, and seconds, set them to 0
  83: //        if (calStart.get(Calendar.YEAR) < 1900)
  84: //        {
  85: //            return BAD_DATE;
  86: //        }
  87: //        else
  88: //        {
  89:         // Because of daylight time saving we cannot use
  90:         //     date.getTime() - calStart.getTimeInMillis()
  91:         // as the difference in milliseconds between 00:00 and 04:00
  92:         // can be 3, 4 or 5 hours but Excel expects it to always
  93:         // be 4 hours.
  94:         // E.g. 2004-03-28 04:00 CEST - 2004-03-28 00:00 CET is 3 hours
  95:         // and 2004-10-31 04:00 CET - 2004-10-31 00:00 CEST is 5 hours
  96:             final double fraction = (((calStart.get(Calendar.HOUR_OF_DAY) * 60
  97:                                  + calStart.get(Calendar.MINUTE)
  98:                                 ) * 60 + calStart.get(Calendar.SECOND)
  99:                                ) * 1000 + calStart.get(Calendar.MILLISECOND)
 100:                               ) / ( double ) DAY_MILLISECONDS;
 101:             calStart = dayStart(calStart);
 102: 
 103:             return fraction + ( double ) absoluteDay(calStart)
 104:                    - CAL_1900_ABSOLUTE;
 105:         }
 106: //    }
 107: 
 108:     /**
 109:      * Given a excel date, converts it into a Date.
 110:      * Assumes 1900 date windowing.
 111:      *
 112:      * @param  date the Excel Date
 113:      *
 114:      * @return Java representation of a date (null if error)
 115:      * @see #getJavaDate(double,boolean)
 116:      */
 117: 
 118:     public static Date getJavaDate(final double date)
 119:     {
 120:       final String dateSystem = LibFormulaBoot.getInstance().getGlobalConfig()
 121:         .getConfigProperty("org.jfree.formula.datesystem.1904", "false");
 122:       return getJavaDate(date, "true".equals(dateSystem));
 123:     }
 124: 
 125:     /**
 126:      *  Given an Excel date with either 1900 or 1904 date windowing,
 127:      *  converts it to a java.util.Date.
 128:      *
 129:      *  NOTE: If the default <code>TimeZone</code> in Java uses Daylight
 130:      *  Saving Time then the conversion back to an Excel date may not give
 131:      *  the same value, that is the comparison
 132:      *  <CODE>excelDate == getExcelDate(getJavaDate(excelDate,false))</CODE>
 133:      *  is not always true. For example if default timezone is
 134:      *  <code>Europe/Copenhagen</code>, on 2004-03-28 the minute after
 135:      *  01:59 CET is 03:00 CEST, if the excel date represents a time between
 136:      *  02:00 and 03:00 then it is converted to past 03:00 summer time
 137:      *
 138:      *  @param date  The Excel date.
 139:      *  @param use1904windowing  true if date uses 1904 windowing,
 140:      *   or false if using 1900 date windowing.
 141:      *  @return Java representation of the date, or null if date is not a valid Excel date
 142:      *  @see java.util.TimeZone
 143:      */
 144:     public static Date getJavaDate(final double date, final boolean use1904windowing) {
 145:         if (isValidExcelDate(date)) {
 146:             int startYear = 1900;
 147:             int dayAdjust = -1; // Excel thinks 2/29/1900 is a valid date, which it isn't
 148:             final int wholeDays = (int)Math.floor(date);
 149:             if (use1904windowing) {
 150:                 startYear = 1904;
 151:                 dayAdjust = 1; // 1904 date windowing uses 1/2/1904 as the first day
 152:             }
 153:             else if (wholeDays < 61) {
 154:                 // Date is prior to 3/1/1900, so adjust because Excel thinks 2/29/1900 exists
 155:                 // If Excel date == 2/29/1900, will become 3/1/1900 in Java representation
 156:                 dayAdjust = 0;
 157:             }
 158:             final GregorianCalendar calendar = new GregorianCalendar(startYear,0,
 159:                                                      wholeDays + dayAdjust);
 160:             final int millisecondsInDay = (int)((date - Math.floor(date)) *
 161:                                           (double) DAY_MILLISECONDS + 0.5);
 162:             calendar.set(GregorianCalendar.MILLISECOND, millisecondsInDay);
 163:             return calendar.getTime();
 164:         }
 165:         else {
 166:             return null;
 167:         }
 168:     }
 169: 
 170:     /**
 171:      * given a format ID this will check whether the format represents
 172:      * an internal date format or not.
 173:      */
 174:     public static boolean isInternalDateFormat(final int format) {
 175:       boolean retval;
 176: 
 177:             switch(format) {
 178:                 // Internal Date Formats as described on page 427 in
 179:                 // Microsoft Excel Dev's Kit...
 180:                 case 0x0e:
 181:                 case 0x0f:
 182:                 case 0x10:
 183:                 case 0x11:
 184:                 case 0x12:
 185:                 case 0x13:
 186:                 case 0x14:
 187:                 case 0x15:
 188:                 case 0x16:
 189:                 case 0x2d:
 190:                 case 0x2e:
 191:                 case 0x2f:
 192:                     retval = true;
 193:                     break;
 194: 
 195:                 default:
 196:                     retval = false;
 197:                     break;
 198:             }
 199:        return retval;
 200:     }
 201: 
 202: 
 203: 
 204:     /**
 205:      * Given a double, checks if it is a valid Excel date.
 206:      *
 207:      * @return true if valid
 208:      * @param  value the double value
 209:      */
 210: 
 211:     public static boolean isValidExcelDate(final double value)
 212:     {
 213:         return (value > -Double.MIN_VALUE);
 214:     }
 215: 
 216:     /**
 217:      * Given a Calendar, return the number of days since 1600/12/31.
 218:      *
 219:      * @return days number of days since 1600/12/31
 220:      * @param  cal the Calendar
 221:      * @exception IllegalArgumentException if date is invalid
 222:      */
 223: 
 224:     private static int absoluteDay(final Calendar cal)
 225:     {
 226:         return cal.get(Calendar.DAY_OF_YEAR)
 227:                + daysInPriorYears(cal.get(Calendar.YEAR));
 228:     }
 229: 
 230:     /**
 231:      * Return the number of days in prior years since 1601
 232:      *
 233:      * @return    days  number of days in years prior to yr.
 234:      * @param     yr    a year (1600 < yr < 4000)
 235:      * @exception IllegalArgumentException if year is outside of range.
 236:      */
 237: 
 238:     private static int daysInPriorYears(final int yr)
 239:     {
 240:         if (yr < 1601)
 241:         {
 242:             throw new IllegalArgumentException(
 243:                 "'year' must be 1601 or greater");
 244:         }
 245:         final int y    = yr - 1601;
 246: 
 247:       return 365 * y      // days in prior years
 248:                  + y / 4      // plus julian leap days in prior years
 249:                  - y / 100    // minus prior century years
 250:                  + y / 400;
 251:     }
 252: 
 253:     // set HH:MM:SS fields of cal to 00:00:00:000
 254:     private static Calendar dayStart(final Calendar cal)
 255:     {
 256:         cal.get(Calendar
 257:             .HOUR_OF_DAY);   // force recalculation of internal fields
 258:         cal.set(Calendar.HOUR_OF_DAY, 0);
 259:         cal.set(Calendar.MINUTE, 0);
 260:         cal.set(Calendar.SECOND, 0);
 261:         cal.set(Calendar.MILLISECOND, 0);
 262:         cal.get(Calendar
 263:             .HOUR_OF_DAY);   // force recalculation of internal fields
 264:         return cal;
 265:     }
 266: 
 267:     // ---------------------------------------------------------------------------------------------------------
 268: }