From 3300a38becb0bfe3370526fefd0f2fdf21391a7d Mon Sep 17 00:00:00 2001 From: Nick Burch Date: Sat, 5 Apr 2008 17:49:06 +0000 Subject: [PATCH] Copy HSSFDateUtils to ss.usermodel, and leave a proxy behind git-svn-id: https://svn.apache.org/repos/asf/poi/branches/ooxml@645146 13f79535-47bb-0310-9956-ffa450edef68 --- .../poi/hssf/usermodel/HSSFDateUtil.java | 342 +--------------- .../org/apache/poi/ss/usermodel/DateUtil.java | 376 ++++++++++++++++++ 2 files changed, 382 insertions(+), 336 deletions(-) create mode 100644 src/java/org/apache/poi/ss/usermodel/DateUtil.java diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFDateUtil.java b/src/java/org/apache/poi/hssf/usermodel/HSSFDateUtil.java index ccf67f8d0..b2ed6e985 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFDateUtil.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFDateUtil.java @@ -25,8 +25,8 @@ package org.apache.poi.hssf.usermodel; import java.util.Calendar; -import java.util.Date; -import java.util.GregorianCalendar; + +import org.apache.poi.ss.usermodel.DateUtil; /** * Contains methods for dealing with Excel dates. @@ -39,338 +39,8 @@ import java.util.GregorianCalendar; * @author Pavel Krupets (pkrupets at palmtreebusiness dot com) */ -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; - - /** - * 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(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. - * - * @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(Date date, boolean use1904windowing) { - Calendar calStart = new GregorianCalendar(); - calStart.setTime(date); // If date includes hours, minutes, and seconds, set them to 0 - return internalGetExcelDate(calStart, use1904windowing); - } - /** - * Given a Date in the form of a Calendar, 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 Calendar holding the date to convert - * @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)) - { - 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 - double fraction = (((date.get(Calendar.HOUR_OF_DAY) * 60 - + date.get(Calendar.MINUTE) - ) * 60 + date.get(Calendar.SECOND) - ) * 1000 + date.get(Calendar.MILLISECOND) - ) / ( double ) DAY_MILLISECONDS; - Calendar calStart = dayStart(date); - - double value = fraction + absoluteDay(calStart, use1904windowing); - - if (!use1904windowing && value >= 60) { - value++; - } else if (use1904windowing) { - value--; - } - - return value; - } - } - - /** - * Given an Excel date with using 1900 date windowing, and - * converts it to a java.util.Date. - * - * NOTE: If the default TimeZone in Java uses Daylight - * Saving Time then the conversion back to an Excel date may not give - * the same value, that is the comparison - * excelDate == getExcelDate(getJavaDate(excelDate,false)) - * is not always true. For example if default timezone is - * Europe/Copenhagen, 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 Date getJavaDate(double date) { - return getJavaDate(date, false); - } - /** - * Given an Excel date with either 1900 or 1904 date windowing, - * converts it to a java.util.Date. - * - * NOTE: If the default TimeZone in Java uses Daylight - * Saving Time then the conversion back to an Excel date may not give - * the same value, that is the comparison - * excelDate == getExcelDate(getJavaDate(excelDate,false)) - * is not always true. For example if default timezone is - * Europe/Copenhagen, 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(double date, boolean use1904windowing) { - if (isValidExcelDate(date)) { - int startYear = 1900; - int dayAdjust = -1; // Excel thinks 2/29/1900 is a valid date, which it isn't - 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; - } - GregorianCalendar calendar = new GregorianCalendar(startYear,0, - wholeDays + dayAdjust); - int millisecondsInDay = (int)((date - Math.floor(date)) * - DAY_MILLISECONDS + 0.5); - calendar.set(GregorianCalendar.MILLISECOND, millisecondsInDay); - return calendar.getTime(); - } - else { - return null; - } - } - - /** - * Given a format ID and its format String, will check to see if the - * format represents a date format or not. - * Firstly, it will check to see if the format ID corresponds to an - * internal excel date format (eg most US date formats) - * If not, it will check to see if the format string only contains - * date formatting characters (ymd-/), which covers most - * non US date formats. - * - * @param formatIndex The index of the format, eg from ExtendedFormatRecord.getFormatIndex - * @param formatString The format string, eg from FormatRecord.getFormatString - * @see #isInternalDateFormat(int) - */ - public static boolean isADateFormat(int formatIndex, String formatString) { - // First up, is this an internal date format? - if(isInternalDateFormat(formatIndex)) { - return true; - } - - // If we didn't get a real string, it can't be - if(formatString == null || formatString.length() == 0) { - return false; - } - - String fs = formatString; - - // Translate \- into just -, before matching - fs = fs.replaceAll("\\\\-","-"); - // And \, into , - fs = fs.replaceAll("\\\\,",","); - // And '\ ' into ' ' - fs = fs.replaceAll("\\\\ "," "); - - // If it end in ;@, that's some crazy dd/mm vs mm/dd - // switching stuff, which we can ignore - fs = fs.replaceAll(";@", ""); - - // If it starts with [$-...], then it is a date, but - // who knows what that starting bit is all about - fs = fs.replaceAll("\\[\\$\\-.*?\\]", ""); - - // Otherwise, check it's only made up, in any case, of: - // y m d h s - / , . : - if(fs.matches("^[yYmMdDhHsS\\-/,. :]+$")) { - return true; - } - - return false; - } - - /** - * Given a format ID this will check whether the format represents - * an internal excel date format or not. - * @see #isADateFormat(int, java.lang.String) - */ - public static boolean isInternalDateFormat(int format) { - boolean retval =false; - - 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; - } - - /** - * Check if a cell contains a date - * Since dates are stored internally in Excel as double values - * we infer it is a date if it is formatted as such. - * @see #isADateFormat(int, String) - * @see #isInternalDateFormat(int) - */ - public static boolean isCellDateFormatted(HSSFCell cell) { - if (cell == null) return false; - boolean bDate = false; - - double d = cell.getNumericCellValue(); - if ( HSSFDateUtil.isValidExcelDate(d) ) { - HSSFCellStyle style = cell.getCellStyle(); - int i = style.getDataFormat(); - String f = style.getDataFormatString(); - bDate = isADateFormat(i, f); - } - return bDate; - } - /** - * Check if a cell contains a date, checking only for internal - * excel date formats. - * As Excel stores a great many of its dates in "non-internal" - * date formats, you will not normally want to use this method. - * @see #isADateFormat(int,String) - * @see #isInternalDateFormat(int) - */ - public static boolean isCellInternalDateFormatted(HSSFCell cell) { - if (cell == null) return false; - boolean bDate = false; - - double d = cell.getNumericCellValue(); - if ( HSSFDateUtil.isValidExcelDate(d) ) { - HSSFCellStyle style = cell.getCellStyle(); - int i = style.getDataFormat(); - bDate = isInternalDateFormat(i); - } - return bDate; - } - - - /** - * Given a double, checks if it is a valid Excel date. - * - * @return true if valid - * @param value the double value - */ - - public static boolean isValidExcelDate(double value) - { - return (value > -Double.MIN_VALUE); - } - - /** - * Given a Calendar, return the number of days since 1900/12/31. - * - * @return days number of days since 1900/12/31 - * @param cal the Calendar - * @exception IllegalArgumentException if date is invalid - */ - - static int absoluteDay(Calendar cal, boolean use1904windowing) - { - return cal.get(Calendar.DAY_OF_YEAR) - + daysInPriorYears(cal.get(Calendar.YEAR), use1904windowing); - } - - /** - * Return the number of days in prior years since 1900 - * - * @return days number of days in years prior to yr. - * @param yr a year (1900 < yr < 4000) - * @param use1904windowing - * @exception IllegalArgumentException if year is outside of range. - */ - - private static int daysInPriorYears(int yr, boolean use1904windowing) - { - if ((!use1904windowing && yr < 1900) || (use1904windowing && yr < 1900)) { - throw new IllegalArgumentException("'year' must be 1900 or greater"); - } - - int yr1 = yr - 1; - int leapDays = yr1 / 4 // plus julian leap days in prior years - - yr1 / 100 // minus prior century years - + yr1 / 400 // plus years divisible by 400 - - 460; // leap days in previous 1900 years - - return 365 * (yr - (use1904windowing ? 1904 : 1900)) + leapDays; - } - - // 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; - } - - // --------------------------------------------------------------------------------------------------------- +public class HSSFDateUtil extends DateUtil { + protected static int absoluteDay(Calendar cal, boolean use1904windowing) { + return DateUtil.absoluteDay(cal, use1904windowing); + } } diff --git a/src/java/org/apache/poi/ss/usermodel/DateUtil.java b/src/java/org/apache/poi/ss/usermodel/DateUtil.java new file mode 100644 index 000000000..ad3ba25d8 --- /dev/null +++ b/src/java/org/apache/poi/ss/usermodel/DateUtil.java @@ -0,0 +1,376 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + + + +/* + * DateUtil.java + * + * Created on January 19, 2002, 9:30 AM + */ +package org.apache.poi.ss.usermodel; + +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; + +/** + * Contains methods for dealing with Excel dates. + * + * @author Michael Harhen + * @author Glen Stampoultzis (glens at apache.org) + * @author Dan Sherman (dsherman at isisph.com) + * @author Hack Kampbjorn (hak at 2mba.dk) + * @author Alex Jacoby (ajacoby at gmail.com) + * @author Pavel Krupets (pkrupets at palmtreebusiness dot com) + */ + +public class DateUtil +{ + protected DateUtil() + { + } + + private static final int BAD_DATE = + -1; // used to specify that date is invalid + private static final long DAY_MILLISECONDS = 24 * 60 * 60 * 1000; + + /** + * 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(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. + * + * @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(Date date, boolean use1904windowing) { + Calendar calStart = new GregorianCalendar(); + calStart.setTime(date); // If date includes hours, minutes, and seconds, set them to 0 + return internalGetExcelDate(calStart, use1904windowing); + } + /** + * Given a Date in the form of a Calendar, 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 Calendar holding the date to convert + * @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)) + { + 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 + double fraction = (((date.get(Calendar.HOUR_OF_DAY) * 60 + + date.get(Calendar.MINUTE) + ) * 60 + date.get(Calendar.SECOND) + ) * 1000 + date.get(Calendar.MILLISECOND) + ) / ( double ) DAY_MILLISECONDS; + Calendar calStart = dayStart(date); + + double value = fraction + absoluteDay(calStart, use1904windowing); + + if (!use1904windowing && value >= 60) { + value++; + } else if (use1904windowing) { + value--; + } + + return value; + } + } + + /** + * Given an Excel date with using 1900 date windowing, and + * converts it to a java.util.Date. + * + * NOTE: If the default TimeZone in Java uses Daylight + * Saving Time then the conversion back to an Excel date may not give + * the same value, that is the comparison + * excelDate == getExcelDate(getJavaDate(excelDate,false)) + * is not always true. For example if default timezone is + * Europe/Copenhagen, 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 Date getJavaDate(double date) { + return getJavaDate(date, false); + } + /** + * Given an Excel date with either 1900 or 1904 date windowing, + * converts it to a java.util.Date. + * + * NOTE: If the default TimeZone in Java uses Daylight + * Saving Time then the conversion back to an Excel date may not give + * the same value, that is the comparison + * excelDate == getExcelDate(getJavaDate(excelDate,false)) + * is not always true. For example if default timezone is + * Europe/Copenhagen, 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(double date, boolean use1904windowing) { + if (isValidExcelDate(date)) { + int startYear = 1900; + int dayAdjust = -1; // Excel thinks 2/29/1900 is a valid date, which it isn't + 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; + } + GregorianCalendar calendar = new GregorianCalendar(startYear,0, + wholeDays + dayAdjust); + int millisecondsInDay = (int)((date - Math.floor(date)) * + DAY_MILLISECONDS + 0.5); + calendar.set(GregorianCalendar.MILLISECOND, millisecondsInDay); + return calendar.getTime(); + } + else { + return null; + } + } + + /** + * Given a format ID and its format String, will check to see if the + * format represents a date format or not. + * Firstly, it will check to see if the format ID corresponds to an + * internal excel date format (eg most US date formats) + * If not, it will check to see if the format string only contains + * date formatting characters (ymd-/), which covers most + * non US date formats. + * + * @param formatIndex The index of the format, eg from ExtendedFormatRecord.getFormatIndex + * @param formatString The format string, eg from FormatRecord.getFormatString + * @see #isInternalDateFormat(int) + */ + public static boolean isADateFormat(int formatIndex, String formatString) { + // First up, is this an internal date format? + if(isInternalDateFormat(formatIndex)) { + return true; + } + + // If we didn't get a real string, it can't be + if(formatString == null || formatString.length() == 0) { + return false; + } + + String fs = formatString; + + // Translate \- into just -, before matching + fs = fs.replaceAll("\\\\-","-"); + // And \, into , + fs = fs.replaceAll("\\\\,",","); + // And '\ ' into ' ' + fs = fs.replaceAll("\\\\ "," "); + + // If it end in ;@, that's some crazy dd/mm vs mm/dd + // switching stuff, which we can ignore + fs = fs.replaceAll(";@", ""); + + // If it starts with [$-...], then it is a date, but + // who knows what that starting bit is all about + fs = fs.replaceAll("\\[\\$\\-.*?\\]", ""); + + // Otherwise, check it's only made up, in any case, of: + // y m d h s - / , . : + if(fs.matches("^[yYmMdDhHsS\\-/,. :]+$")) { + return true; + } + + return false; + } + + /** + * Given a format ID this will check whether the format represents + * an internal excel date format or not. + * @see #isADateFormat(int, java.lang.String) + */ + public static boolean isInternalDateFormat(int format) { + boolean retval =false; + + 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; + } + + /** + * Check if a cell contains a date + * Since dates are stored internally in Excel as double values + * we infer it is a date if it is formatted as such. + * @see #isADateFormat(int, String) + * @see #isInternalDateFormat(int) + */ + public static boolean isCellDateFormatted(Cell cell) { + if (cell == null) return false; + boolean bDate = false; + + double d = cell.getNumericCellValue(); + if ( DateUtil.isValidExcelDate(d) ) { + CellStyle style = cell.getCellStyle(); + int i = style.getDataFormat(); + String f = style.getDataFormatString(); + bDate = isADateFormat(i, f); + } + return bDate; + } + /** + * Check if a cell contains a date, checking only for internal + * excel date formats. + * As Excel stores a great many of its dates in "non-internal" + * date formats, you will not normally want to use this method. + * @see #isADateFormat(int,String) + * @see #isInternalDateFormat(int) + */ + public static boolean isCellInternalDateFormatted(Cell cell) { + if (cell == null) return false; + boolean bDate = false; + + double d = cell.getNumericCellValue(); + if ( DateUtil.isValidExcelDate(d) ) { + CellStyle style = cell.getCellStyle(); + int i = style.getDataFormat(); + bDate = isInternalDateFormat(i); + } + return bDate; + } + + + /** + * Given a double, checks if it is a valid Excel date. + * + * @return true if valid + * @param value the double value + */ + + public static boolean isValidExcelDate(double value) + { + return (value > -Double.MIN_VALUE); + } + + /** + * Given a Calendar, return the number of days since 1900/12/31. + * + * @return days number of days since 1900/12/31 + * @param cal the Calendar + * @exception IllegalArgumentException if date is invalid + */ + + protected static int absoluteDay(Calendar cal, boolean use1904windowing) + { + return cal.get(Calendar.DAY_OF_YEAR) + + daysInPriorYears(cal.get(Calendar.YEAR), use1904windowing); + } + + /** + * Return the number of days in prior years since 1900 + * + * @return days number of days in years prior to yr. + * @param yr a year (1900 < yr < 4000) + * @param use1904windowing + * @exception IllegalArgumentException if year is outside of range. + */ + + private static int daysInPriorYears(int yr, boolean use1904windowing) + { + if ((!use1904windowing && yr < 1900) || (use1904windowing && yr < 1900)) { + throw new IllegalArgumentException("'year' must be 1900 or greater"); + } + + int yr1 = yr - 1; + int leapDays = yr1 / 4 // plus julian leap days in prior years + - yr1 / 100 // minus prior century years + + yr1 / 400 // plus years divisible by 400 + - 460; // leap days in previous 1900 years + + return 365 * (yr - (use1904windowing ? 1904 : 1900)) + leapDays; + } + + // 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; + } + + // --------------------------------------------------------------------------------------------------------- +}