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