From 73ee6c3d270a91bd447f732b24c4d65169b0c8d6 Mon Sep 17 00:00:00 2001 From: Stephen Colebourne Date: Sun, 8 Jun 2003 23:14:23 +0000 Subject: [PATCH] Rework time package in preparation for 2.0 release DateUtils split to DateFormatUtils and DurationFormatUtils CalendarUtils renamed to DateUtils StopWatch time format method moved to DurationFormatUtils Tests updated and pass git-svn-id: https://svn.apache.org/repos/asf/jakarta/commons/proper/lang/trunk@137361 13f79535-47bb-0310-9956-ffa450edef68 --- .../commons/lang/time/CalendarUtils.java | 506 --------------- .../commons/lang/time/DateFormatUtils.java | 297 +++++++++ .../apache/commons/lang/time/DateUtils.java | 612 +++++++++++++----- .../lang/time/DurationFormatUtils.java | 167 +++++ .../commons/lang/time/FastDateFormat.java | 440 +++++++------ .../apache/commons/lang/time/StopWatch.java | 51 +- .../commons/lang/time/CalendarUtilsTest.java | 379 ----------- .../lang/time/DateFormatUtilsTest.java | 199 ++++++ .../commons/lang/time/DateUtilsTest.java | 357 ++++++++-- .../lang/time/DurationFormatUtilsTest.java | 162 +++++ .../commons/lang/time/FastDateFormatTest.java | 84 ++- .../commons/lang/time/StopWatchTest.java | 31 +- .../commons/lang/time/TimeTestSuite.java | 8 +- 13 files changed, 1912 insertions(+), 1381 deletions(-) delete mode 100644 src/java/org/apache/commons/lang/time/CalendarUtils.java create mode 100644 src/java/org/apache/commons/lang/time/DateFormatUtils.java create mode 100644 src/java/org/apache/commons/lang/time/DurationFormatUtils.java delete mode 100644 src/test/org/apache/commons/lang/time/CalendarUtilsTest.java create mode 100644 src/test/org/apache/commons/lang/time/DateFormatUtilsTest.java create mode 100644 src/test/org/apache/commons/lang/time/DurationFormatUtilsTest.java diff --git a/src/java/org/apache/commons/lang/time/CalendarUtils.java b/src/java/org/apache/commons/lang/time/CalendarUtils.java deleted file mode 100644 index 5b93c8932..000000000 --- a/src/java/org/apache/commons/lang/time/CalendarUtils.java +++ /dev/null @@ -1,506 +0,0 @@ -/* ==================================================================== - * The Apache Software License, Version 1.1 - * - * Copyright (c) 2002-2003 The Apache Software Foundation. All rights - * reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * 3. The end-user documentation included with the redistribution, if - * any, must include the following acknowlegement: - * "This product includes software developed by the - * Apache Software Foundation (http://www.apache.org/)." - * Alternately, this acknowlegement may appear in the software itself, - * if and wherever such third-party acknowlegements normally appear. - * - * 4. The names "The Jakarta Project", "Commons", and "Apache Software - * Foundation" must not be used to endorse or promote products derived - * from this software without prior written permission. For written - * permission, please contact apache@apache.org. - * - * 5. Products derived from this software may not be called "Apache" - * nor may "Apache" appear in their names without prior written - * permission of the Apache Software Foundation. - * - * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR - * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF - * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - * ==================================================================== - * - * This software consists of voluntary contributions made by many - * individuals on behalf of the Apache Software Foundation. For more - * information on the Apache Software Foundation, please see - * . - */ -package org.apache.commons.lang.time; - -import java.text.*; -import java.util.*; - -/** - * A suite of utilities surrounding the use of the Calendar and Date object. - * - * @author Serge Knystautas - * @since 2.1 - * @version $Id: CalendarUtils.java,v 1.3 2003/04/09 01:04:48 ggregory Exp $ - */ -public class CalendarUtils { - - /** - * This is half a month, so this represents whether a date is in the top - * or bottom half of the month. - */ - public final static int SEMI_MONTH = 1001; - - private static final int[][] fields = { - {Calendar.MILLISECOND}, - {Calendar.SECOND}, - {Calendar.MINUTE}, - {Calendar.HOUR_OF_DAY, Calendar.HOUR}, - {Calendar.DATE, Calendar.DAY_OF_MONTH, Calendar.AM_PM /* Calendar.DAY_OF_YEAR, Calendar.DAY_OF_WEEK, Calendar.DAY_OF_WEEK_IN_MONTH */}, - {Calendar.MONTH, CalendarUtils.SEMI_MONTH}, - {Calendar.YEAR}, - {Calendar.ERA}}; - - private static DateFormat[] dateFormats = { - //3/31/92 10:00:07 PST - new SimpleDateFormat("M/dd/yy h:mm:ss z"), - //January 23, 1987 10:05pm - new SimpleDateFormat("MMM d, yyyy h:mm a"), - //22:00 GMT - new SimpleDateFormat("h:mm z")}; - - /** - * A week range, starting on Sunday. - */ - public final static int RANGE_WEEK_SUNDAY = 1; - - /** - * A week range, starting on Monday. - */ - public final static int RANGE_WEEK_MONDAY = 2; - - /** - * A week range, starting on the day focused. - */ - public final static int RANGE_WEEK_RELATIVE = 3; - - /** - * A week range, centered around the day focused. - */ - public final static int RANGE_WEEK_CENTER = 4; - - /** - * A month range, the week starting on Sunday. - */ - public final static int RANGE_MONTH_SUNDAY = 5; - - /** - * A month range, the week starting on Monday. - */ - public final static int RANGE_MONTH_MONDAY = 6; - - /** - * See the other round method. Works with a Date object. - */ - public static Date round(Date val, int field) { - GregorianCalendar gval = new GregorianCalendar(); - gval.setTime(val); - modify(gval, field, true); - return gval.getTime(); - } - - /** - * Round this date, leaving the field specified as the most significant - * field. For example, if you had the datetime of 28 Mar 2002 - * 13:45:01.231, if this was passed with HOUR, it would return 28 Mar - * 2002 14:00:00.000. If this was passed with MONTH, it would return - * 1 April 2002 0:00:00.000. - */ - public static Calendar round(Calendar val, int field) { - Calendar rounded = (Calendar) val.clone(); - modify(rounded, field, true); - return rounded; - } - - /** - * See the other round method. Works with an Object, trying to - * use it as either a Date or Calendar. - */ - public static Date round(Object val, int field) { - if (val instanceof Date) { - return round((Date) val, field); - } else if (val instanceof Calendar) { - return round((Calendar) val, field).getTime(); - } else { - throw new ClassCastException("Could not round " + val); - } - } - - /** - * See the other trunc method. Works with a Date. - */ - public static Date trunc(Date val, int field) { - GregorianCalendar gval = new GregorianCalendar(); - gval.setTime(val); - modify(gval, field, false); - return gval.getTime(); - } - - /** - * Truncate this date, leaving the field specified as the most significant - * field. For example, if you had the datetime of 28 Mar 2002 - * 13:45:01.231, if you passed with HOUR, it would return 28 Mar - * 2002 13:00:00.000. If this was passed with MONTH, it would return - * 1 Mar 2002 0:00:00.000. - */ - public static Calendar trunc(Calendar val, int field) { - Calendar truncated = (Calendar) val.clone(); - modify(truncated, field, false); - return truncated; - } - - /** - * See the other trunc method. Works with an Object, trying to - * use it as either a Date or Calendar. - */ - public static Date trunc(Object val, int field) { - if (val instanceof Date) { - return trunc((Date) val, field); - } else if (val instanceof Calendar) { - return trunc((Calendar) val, field).getTime(); - } else { - throw new ClassCastException("Could not trunc " + val); - } - } - - private static void modify(Calendar val, int field, boolean round) { - boolean roundUp = false; - for (int i = 0; i < fields.length; i++) { - for (int j = 0; j < fields[i].length; j++) { - if (fields[i][j] == field) { - //This is our field... we stop looping - if (round && roundUp) { - if (field == CalendarUtils.SEMI_MONTH) { - //This is a special case that's hard to generalize - //If the date is 1, we round up to 16, otherwise - // we subtract 15 days and add 1 month - if (val.get(Calendar.DATE) == 1) { - val.add(Calendar.DATE, 15); - } else { - val.add(Calendar.DATE, -15); - val.add(Calendar.MONTH, 1); - } - } else { - //We need at add one to this field since the - // last number causes us to round up - val.add(fields[i][0], 1); - } - } - return; - } - } - //We have various fields that are not easy roundings - int offset = 0; - boolean offsetSet = false; - //These are special types of fields that require different rounding rules - switch (field) { - case CalendarUtils.SEMI_MONTH: - if (fields[i][0] == Calendar.DATE) { - //If we're going to drop the DATE field's value, - // we want to do this our own way. - //We need to subtrace 1 since the date has a minimum of 1 - offset = val.get(Calendar.DATE) - 1; - //If we're above 15 days adjustment, that means we're in the - // bottom half of the month and should stay accordingly. - if (offset >= 15) { - offset -= 15; - } - //Record whether we're in the top or bottom half of that range - roundUp = offset > 7; - offsetSet = true; - } - break; - case Calendar.AM_PM: - if (fields[i][0] == Calendar.HOUR) { - //If we're going to drop the HOUR field's value, - // we want to do this our own way. - offset = val.get(Calendar.HOUR); - if (offset >= 12) { - offset -= 12; - } - roundUp = offset > 6; - offsetSet = true; - } - break; - } - if (!offsetSet) { - int min = val.getActualMinimum(fields[i][0]); - int max = val.getActualMaximum(fields[i][0]); - //Calculate the offset from the minimum allowed value - offset = val.get(fields[i][0]) - min; - //Set roundUp if this is more than half way between the minimum and maximum - roundUp = offset > ((max - min) / 2); - } - //We need to remove this field - val.add(fields[i][0], -offset); - } - throw new RuntimeException("We do not support that field."); - - } - - /** - * Parses strings the way that CVS supports it (very human readable). - */ - public static Calendar parse(String original) { - return parse(original, Locale.getDefault()); - } - - /** - * Parses strings the way that CVS supports it (very human readable). - */ - public static Calendar parse(String original, Locale locale) { - //Get the symbol names - DateFormatSymbols symbols = new DateFormatSymbols(locale); - - //Prep the string to parse - String value = original.toLowerCase().trim(); - - //Get the current date/time - Calendar now = Calendar.getInstance(); - if (value.endsWith(" ago")) { - //If this was a date that was "ago" the current time... - //Strip out the ' ago' part - value = value.substring(0, value.length() - 4); - - //Split the value and unit - int start = value.indexOf(" "); - if (start < 0) { - throw new RuntimeException("Could not find space in between value and unit"); - } - String unit = value.substring(start + 1); - value = value.substring(0, start); - //We support "a week", so we need to parse the value as "a" - int val = 0; - if (value.equals("a") || value.equals("an")) { - val = 1; - } else { - val = Integer.parseInt(value); - } - - //Determine the unit - if (unit.equals("milliseconds") || unit.equals("millisecond")) { - now.add(Calendar.MILLISECOND, -val); - } else if (unit.equals("seconds") || unit.equals("second")) { - now.add(Calendar.SECOND, -val); - } else if (unit.equals("minutes") || unit.equals("minute")) { - now.add(Calendar.MINUTE, -val); - } else if (unit.equals("hours") || unit.equals("hour")) { - now.add(Calendar.HOUR, -val); - } else if (unit.equals("days") || unit.equals("day")) { - now.add(Calendar.DATE, -val); - } else if (unit.equals("weeks") || unit.equals("week")) { - now.add(Calendar.DATE, -val * 7); - } else if (unit.equals("fortnights") || unit.equals("fortnight")) { - now.add(Calendar.DATE, -val * 14); - } else if (unit.equals("months") || unit.equals("month")) { - now.add(Calendar.MONTH, -val); - } else if (unit.equals("years") || unit.equals("year")) { - now.add(Calendar.YEAR, -val); - } else { - throw new RuntimeException("We do not understand that many units ago"); - } - return now; - } else if (value.startsWith("last ")) { - //If this was the last time a certain field was met - //Strip out the 'last ' part - value = value.substring(5); - //Get the current date/time - String[] strings = symbols.getWeekdays(); - for (int i = 0; i < strings.length; i++) { - if (value.equalsIgnoreCase(strings[i])) { - //How many days after Sunday - int daysAgo = now.get(Calendar.DAY_OF_WEEK) - i; - if (daysAgo <= 0) { - daysAgo += 7; - } - now.add(Calendar.DATE, -daysAgo); - return now; - } - } - strings = symbols.getMonths(); - for (int i = 0; i < strings.length; i++) { - if (value.equalsIgnoreCase(strings[i])) { - //How many days after January - int monthsAgo = now.get(Calendar.MONTH) - i; - if (monthsAgo <= 0) { - monthsAgo += 12; - } - now.add(Calendar.MONTH, -monthsAgo); - return now; - } - } - if (value.equals("week")) { - now.add(Calendar.DATE, -7); - return now; - } - } else if (value.equals("yesterday")) { - now.add(Calendar.DATE, -1); - return now; - } else if (value.equals("tomorrow")) { - now.add(Calendar.DATE, 1); - return now; - } - //Try to parse the date a number of different ways - for (int i = 0; i < dateFormats.length; i++) { - try { - Date datetime = dateFormats[i].parse(original); - Calendar cal = Calendar.getInstance(); - cal.setTime(datetime); - return cal; - } catch (ParseException pe) { - //we ignore this and just keep trying - } - } - - throw new RuntimeException("Unable to parse '" + original + "'."); - } - - /** - * This constructs an Iterator that will start and stop over a date - * range based on the focused date and the range style. For instance, - * passing Thursday, July 4, 2002 and a RANGE_MONTH_SUNDAY will return - * an Iterator that starts with Sunday, June 30, 2002 and ends with - * Saturday, August 3, 2002. - */ - public static Iterator getCalendarIterator(Calendar focus, int rangeStyle) { - Calendar start = null; - Calendar end = null; - int startCutoff = Calendar.SUNDAY; - int endCutoff = Calendar.SATURDAY; - switch (rangeStyle) { - case RANGE_MONTH_SUNDAY: - case RANGE_MONTH_MONDAY: - //Set start to the first of the month - start = trunc(focus, Calendar.MONTH); - //Set end to the last of the month - end = (Calendar) start.clone(); - end.add(Calendar.MONTH, 1); - end.add(Calendar.DATE, -1); - //Loop start back to the previous sunday or monday - if (rangeStyle == RANGE_MONTH_MONDAY) { - startCutoff = Calendar.MONDAY; - endCutoff = Calendar.SUNDAY; - } - break; - case RANGE_WEEK_SUNDAY: - case RANGE_WEEK_MONDAY: - case RANGE_WEEK_RELATIVE: - case RANGE_WEEK_CENTER: - //Set start and end to the current date - start = trunc(focus, Calendar.DATE); - end = trunc(focus, Calendar.DATE); - switch (rangeStyle) { - case RANGE_WEEK_SUNDAY: - //already set by default - break; - case RANGE_WEEK_MONDAY: - startCutoff = Calendar.MONDAY; - endCutoff = Calendar.SUNDAY; - break; - case RANGE_WEEK_RELATIVE: - startCutoff = focus.get(Calendar.DAY_OF_WEEK); - endCutoff = startCutoff - 1; - break; - case RANGE_WEEK_CENTER: - startCutoff = focus.get(Calendar.DAY_OF_WEEK) - 3; - endCutoff = focus.get(Calendar.DAY_OF_WEEK) + 3; - break; - } - break; - default: - throw new RuntimeException("The range style " + rangeStyle + " is not valid."); - } - if (startCutoff < Calendar.SUNDAY) { - startCutoff += 7; - } - if (endCutoff > Calendar.SATURDAY) { - endCutoff -= 7; - } - while (start.get(Calendar.DAY_OF_WEEK) != startCutoff) { - start.add(Calendar.DATE, -1); - } - while (end.get(Calendar.DAY_OF_WEEK) != endCutoff) { - end.add(Calendar.DATE, 1); - } - final Calendar startFinal = start; - final Calendar endFinal = end; - Iterator it = new Iterator() { - Calendar spot = null; - { - spot = startFinal; - spot.add(Calendar.DATE, -1); - } - - public boolean hasNext() { - return spot.before(endFinal); - } - - public Object next() { - if (spot.equals(endFinal)) { - throw new NoSuchElementException(); - } - spot.add(Calendar.DATE, 1); - return spot.clone(); - } - - public void remove() { - throw new UnsupportedOperationException(); - } - }; - return it; - } - - /** - * See the other getCalendarIterator. Works with a Date. - */ - public static Iterator getCalendarIterator(Date focus, int rangeStyle) { - GregorianCalendar gval = new GregorianCalendar(); - gval.setTime(focus); - return getCalendarIterator(gval, rangeStyle); - } - - /** - * See the other getCalendarIterator. Works with an Object, trying - * to use it as a Date or Calendar. - */ - public static Iterator getCalendarIterator(Object focus, int rangeStyle) { - if (focus instanceof Date) { - return getCalendarIterator((Date) focus, rangeStyle); - } else if (focus instanceof Calendar) { - return getCalendarIterator((Calendar) focus, rangeStyle); - } else { - throw new ClassCastException("Could not iterate based on " + focus); - } - } - -} diff --git a/src/java/org/apache/commons/lang/time/DateFormatUtils.java b/src/java/org/apache/commons/lang/time/DateFormatUtils.java new file mode 100644 index 000000000..1eaa996fc --- /dev/null +++ b/src/java/org/apache/commons/lang/time/DateFormatUtils.java @@ -0,0 +1,297 @@ +/* ==================================================================== + * The Apache Software License, Version 1.1 + * + * Copyright (c) 2002-2003 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowlegement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowlegement may appear in the software itself, + * if and wherever such third-party acknowlegements normally appear. + * + * 4. The names "The Jakarta Project", "Commons", and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Software Foundation. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + */ +package org.apache.commons.lang.time; + +import java.util.Date; +import java.util.Locale; +import java.util.TimeZone; + +/** + * Date and time formatting utilites and constants. + *

+ * Formatting is performed using the + * {@link org.apache.commons.lang.time.FastDateFormat} class. + * + * @author Apache Ant - DateUtils + * @author Stephane Bailliez + * @author Stefan Bodewig + * @author Stephen Colebourne + * @since 2.0 + * @version $Id: DateFormatUtils.java,v 1.1 2003/06/08 23:14:23 scolebourne Exp $ + */ +public final class DateFormatUtils { + + /** + * ISO8601 formatter for date-time witout timezone. + * The format used is yyyy-MM-dd'T'HH:mm:ss. + */ + public static final FastDateFormat ISO_DATETIME_FORMAT + = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss"); + + /** + * ISO8601 formatter for date-time with timezone. + * The format used is yyyy-MM-dd'T'HH:mm:ssZZ. + */ + public static final FastDateFormat ISO_DATETIME_TIMEZONE_FORMAT + = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ssZZ"); + + /** + * ISO8601 formatter for date without timezone. + * The format used is yyyy-MM-dd. + */ + public static final FastDateFormat ISO_DATE_FORMAT + = FastDateFormat.getInstance("yyyy-MM-dd"); + + /** + * ISO8601-like formatter for date with timezone. + * The format used is yyyy-MM-ddZZ. + * This pattern does not comply with the formal ISO8601 specification + * as the standard does not allow a timezone without a time. + */ + public static final FastDateFormat ISO_DATE_TIMEZONE_FORMAT + = FastDateFormat.getInstance("yyyy-MM-ddZZ"); + + /** + * ISO8601 formatter for time without timezone. + * The format used is 'T'HH:mm:ss. + */ + public static final FastDateFormat ISO_TIME_FORMAT + = FastDateFormat.getInstance("'T'HH:mm:ss"); + + /** + * ISO8601 formatter for time with timezone. + * The format used is 'T'HH:mm:ssZZ. + */ + public static final FastDateFormat ISO_TIME_TIMEZONE_FORMAT + = FastDateFormat.getInstance("'T'HH:mm:ssZZ"); + + /** + * ISO8601-like formatter for time without timezone. + * The format used is HH:mm:ss. + * This pattern does not comply with the formal ISO8601 specification + * as the standard requires the 'T' prefix for times. + */ + public static final FastDateFormat ISO_TIME_NO_T_FORMAT + = FastDateFormat.getInstance("HH:mm:ss"); + + /** + * ISO8601-like formatter for time with timezone. + * The format used is HH:mm:ssZZ. + * This pattern does not comply with the formal ISO8601 specification + * as the standard requires the 'T' prefix for times. + */ + public static final FastDateFormat ISO_TIME_NO_T_TIMEZONE_FORMAT + = FastDateFormat.getInstance("HH:mm:ssZZ"); + + /** + * SMTP (and probably other) date headers. + * The format used is EEE, dd MMM yyyy HH:mm:ss Z in US locale. + */ + public static final FastDateFormat SMTP_DATETIME_FORMAT + = FastDateFormat.getInstance("EEE, dd MMM yyyy HH:mm:ss Z", Locale.US); + + //----------------------------------------------------------------------- + /** + * DateFormatUtils instances should NOT be constructed in standard programming. + *

+ * This constructor is public to permit tools that require a JavaBean instance + * to operate. + */ + public DateFormatUtils() { + } + + /** + * Format a date/time into a specific pattern using the UTC timezone. + * + * @param millis the date to format expressed in milliseconds + * @param pattern the pattern to use to format the date + * @return the formatted date + */ + public static String formatUTC(long millis, String pattern) { + return format(new Date(millis), pattern, DateUtils.UTC_TIMEZONE, null); + } + + /** + * Format a date/time into a specific pattern using the UTC timezone. + * + * @param date the date to format + * @param pattern the pattern to use to format the date + * @return the formatted date + */ + public static String formatUTC(Date date, String pattern) { + return format(date, pattern, DateUtils.UTC_TIMEZONE, null); + } + + /** + * Format a date/time into a specific pattern using the UTC timezone. + * + * @param millis the date to format expressed in milliseconds + * @param pattern the pattern to use to format the date + * @param locale the locale to use, may be null + * @return the formatted date + */ + public static String formatUTC(long millis, String pattern, Locale locale) { + return format(new Date(millis), pattern, DateUtils.UTC_TIMEZONE, locale); + } + + /** + * Format a date/time into a specific pattern using the UTC timezone. + * + * @param date the date to format + * @param pattern the pattern to use to format the date + * @param locale the locale to use, may be null + * @return the formatted date + */ + public static String formatUTC(Date date, String pattern, Locale locale) { + return format(date, pattern, DateUtils.UTC_TIMEZONE, locale); + } + + /** + * Format a date/time into a specific pattern. + * + * @param millis the date to format expressed in milliseconds + * @param pattern the pattern to use to format the date + * @return the formatted date + */ + public static String format(long millis, String pattern) { + return format(new Date(millis), pattern, null, null); + } + + /** + * Format a date/time into a specific pattern. + * + * @param date the date to format + * @param pattern the pattern to use to format the date + * @return the formatted date + */ + public static String format(Date date, String pattern) { + return format(date, pattern, null, null); + } + + /** + * Format a date/time into a specific pattern in a timezone. + * + * @param millis the time expressed in milliseconds + * @param pattern the pattern to use to format the date + * @param timeZone the timezone to use, may be null + * @return the formatted date + */ + public static String format(long millis, String pattern, TimeZone timeZone) { + return format(new Date(millis), pattern, timeZone, null); + } + + /** + * Format a date/time into a specific pattern in a timezone. + * + * @param date the date to format + * @param pattern the pattern to use to format the date + * @param timeZone the timezone to use, may be null + * @return the formatted date + */ + public static String format(Date date, String pattern, TimeZone timeZone) { + return format(date, pattern, timeZone, null); + } + + /** + * Format a date/time into a specific pattern in a locale. + * + * @param millis the date to format expressed in milliseconds + * @param pattern the pattern to use to format the date + * @param locale the locale to use, may be null + * @return the formatted date + */ + public static String format(long millis, String pattern, Locale locale) { + return format(new Date(millis), pattern, null, locale); + } + + /** + * Format a date/time into a specific pattern in a locale. + * + * @param date the date to format + * @param pattern the pattern to use to format the date + * @param locale the locale to use, may be null + * @return the formatted date + */ + public static String format(Date date, String pattern, Locale locale) { + return format(date, pattern, null, locale); + } + + /** + * Format a date/time into a specific pattern in a timezone and locale. + * + * @param millis the date to format expressed in milliseconds + * @param pattern the pattern to use to format the date + * @param timeZone the timezone to use, may be null + * @param locale the locale to use, may be null + * @return the formatted date + */ + public static String format(long millis, String pattern, TimeZone timeZone, Locale locale) { + return format(new Date(millis), pattern, timeZone, locale); + } + + /** + * Format a date/time into a specific pattern in a timezone and locale. + * + * @param date the date to format + * @param pattern the pattern to use to format the date + * @param timeZone the timezone to use, may be null + * @param locale the locale to use, may be null + * @return the formatted date + */ + public static String format(Date date, String pattern, TimeZone timeZone, Locale locale) { + FastDateFormat df = FastDateFormat.getInstance(pattern, timeZone, locale); + return df.format(date); + } + +} diff --git a/src/java/org/apache/commons/lang/time/DateUtils.java b/src/java/org/apache/commons/lang/time/DateUtils.java index 8364a4d1c..89230dec5 100644 --- a/src/java/org/apache/commons/lang/time/DateUtils.java +++ b/src/java/org/apache/commons/lang/time/DateUtils.java @@ -53,209 +53,491 @@ */ package org.apache.commons.lang.time; -import java.text.ChoiceFormat; import java.text.DateFormat; -import java.text.MessageFormat; +import java.text.DateFormatSymbols; +import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; +import java.util.GregorianCalendar; +import java.util.Iterator; import java.util.Locale; +import java.util.NoSuchElementException; import java.util.TimeZone; /** - * Helper methods to deal with date/time formatting. [Relies heavily on - * code taken from the DateUtils class of the jakarata-ant project.] + * A suite of utilities surrounding the use of the Calendar and Date object. * - * @author Stephane Bailliez - * @author Stefan Bodewig - * @since 2.1 - * @version $Id: DateUtils.java,v 1.2 2003/02/04 22:19:33 scolebourne Exp $ + * @author Serge Knystautas + * @author Stephen Colebourne + * @since 2.0 + * @version $Id: DateUtils.java,v 1.3 2003/06/08 23:14:23 scolebourne Exp $ */ -public final class DateUtils { +public class DateUtils { + + /** + * The UTC timezone (often referred to as GMT). + */ + public static final TimeZone UTC_TIMEZONE = TimeZone.getTimeZone("GMT"); + /** + * Number of milliseconds in a standard second. + */ + public static final int MILLIS_IN_SECOND = 1000; + /** + * Number of milliseconds in a standard minute. + */ + public static final int MILLIS_IN_MINUTE = 60 * 1000; + /** + * Number of milliseconds in a standard hour. + */ + public static final int MILLIS_IN_HOUR = 60 * 60 * 1000; + /** + * Number of milliseconds in a standard day. + */ + public static final int MILLIS_IN_DAY = 24 * 60 * 60 * 1000; /** - * ISO8601-like pattern for date-time. It does not support timezone. - * yyyy-MM-ddTHH:mm:ss + * This is half a month, so this represents whether a date is in the top + * or bottom half of the month. */ - public static final String ISO8601_DATETIME_PATTERN - = "yyyy-MM-dd'T'HH:mm:ss"; + public final static int SEMI_MONTH = 1001; + + private static final int[][] fields = { + {Calendar.MILLISECOND}, + {Calendar.SECOND}, + {Calendar.MINUTE}, + {Calendar.HOUR_OF_DAY, Calendar.HOUR}, + {Calendar.DATE, Calendar.DAY_OF_MONTH, Calendar.AM_PM /* Calendar.DAY_OF_YEAR, Calendar.DAY_OF_WEEK, Calendar.DAY_OF_WEEK_IN_MONTH */}, + {Calendar.MONTH, DateUtils.SEMI_MONTH}, + {Calendar.YEAR}, + {Calendar.ERA}}; + + private static DateFormat[] dateFormats = { + //3/31/92 10:00:07 PST + new SimpleDateFormat("M/dd/yy h:mm:ss z"), + //January 23, 1987 10:05pm + new SimpleDateFormat("MMM d, yyyy h:mm a"), + //22:00 GMT + new SimpleDateFormat("h:mm z")}; /** - * ISO8601-like pattern for date. yyyy-MM-dd + * A week range, starting on Sunday. */ - public static final String ISO8601_DATE_PATTERN - = "yyyy-MM-dd"; + public final static int RANGE_WEEK_SUNDAY = 1; /** - * ISO8601-like pattern for time. HH:mm:ss + * A week range, starting on Monday. */ - public static final String ISO8601_TIME_PATTERN - = "HH:mm:ss"; + public final static int RANGE_WEEK_MONDAY = 2; /** - * Format used for SMTP (and probably other) Date headers. + * A week range, starting on the day focused. */ - public static final DateFormat DATE_HEADER_FORMAT - = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss ", Locale.US); + public final static int RANGE_WEEK_RELATIVE = 3; + /** + * A week range, centered around the day focused. + */ + public final static int RANGE_WEEK_CENTER = 4; -// code from Magesh moved from DefaultLogger and slightly modified - private static final MessageFormat MINUTE_SECONDS - = new MessageFormat("{0}{1}"); + /** + * A month range, the week starting on Sunday. + */ + public final static int RANGE_MONTH_SUNDAY = 5; - private static final double[] LIMITS = {0, 1, 2}; + /** + * A month range, the week starting on Monday. + */ + public final static int RANGE_MONTH_MONDAY = 6; - private static final String[] MINUTES_PART = - {"", "1 minute ", "{0,number} minutes "}; - - private static final String[] SECONDS_PART = - {"0 seconds", "1 second", "{1,number} seconds"}; - - private static final ChoiceFormat MINUTES_FORMAT = - new ChoiceFormat(LIMITS, MINUTES_PART); - - private static final ChoiceFormat SECONDS_FORMAT = - new ChoiceFormat(LIMITS, SECONDS_PART); - - static { - MINUTE_SECONDS.setFormat(0, MINUTES_FORMAT); - MINUTE_SECONDS.setFormat(1, SECONDS_FORMAT); + /** + * See the other round method. Works with a Date object. + */ + public static Date round(Date val, int field) { + GregorianCalendar gval = new GregorianCalendar(); + gval.setTime(val); + modify(gval, field, true); + return gval.getTime(); } /** - *

DateUtils instances should NOT be constructed in standard programming.

- * - *

This constructor is public to permit tools that require a JavaBean instance - * to operate.

+ * Round this date, leaving the field specified as the most significant + * field. For example, if you had the datetime of 28 Mar 2002 + * 13:45:01.231, if this was passed with HOUR, it would return 28 Mar + * 2002 14:00:00.000. If this was passed with MONTH, it would return + * 1 April 2002 0:00:00.000. */ - public DateUtils() { - } - - - /** - * Format a date/time into a specific pattern. - * @param date the date to format expressed in milliseconds. - * @param pattern the pattern to use to format the date. - * @return the formatted date. - */ - public static String format(long date, String pattern) { - return format(new Date(date), pattern); - } - - - /** - * Format a date/time into a specific pattern. - * @param date the date to format expressed in milliseconds. - * @param pattern the pattern to use to format the date. - * @return the formatted date. - */ - public static String format(Date date, String pattern) { - DateFormat df = createDateFormat(pattern); - return df.format(date); - } - - - /** - * Format an elapsed time into a plurialization correct string. - * It is limited only to report elapsed time in minutes and - * seconds and has the following behavior. - *
    - *
  • minutes are not displayed when 0. (ie: "45 seconds")
  • - *
  • seconds are always displayed in plural form (ie "0 seconds" or - * "10 seconds") except for 1 (ie "1 second")
  • - *
- * @param time the elapsed time to report in milliseconds. - * @return the formatted text in minutes/seconds. - */ - public static String formatElapsedTime(long millis) { - long seconds = millis / 1000; - long minutes = seconds / 60; - Object[] args = {new Long(minutes), new Long(seconds % 60)}; - return MINUTE_SECONDS.format(args); + public static Calendar round(Calendar val, int field) { + Calendar rounded = (Calendar) val.clone(); + modify(rounded, field, true); + return rounded; } /** - * return a lenient date format set to GMT time zone. - * @param pattern the pattern used for date/time formatting. - * @return the configured format for this pattern. + * See the other round method. Works with an Object, trying to + * use it as either a Date or Calendar. */ - private static DateFormat createDateFormat(String pattern) { - SimpleDateFormat sdf = new SimpleDateFormat(pattern); - TimeZone gmt = TimeZone.getTimeZone("GMT"); - sdf.setTimeZone(gmt); - sdf.setLenient(true); - return sdf; - } - - /** - * Calculate the phase of the moon for a given date. - * - *

Code heavily influenced by hacklib.c in Nethack

- * - *

The Algorithm: - * - *

-     * moon period = 29.53058 days ~= 30, year = 365.2422 days
-     *
-     * days moon phase advances on first day of year compared to preceding year
-     *  = 365.2422 - 12*29.53058 ~= 11
-     *
-     * years in Metonic cycle (time until same phases fall on the same days of
-     *  the month) = 18.6 ~= 19
-     *
-     * moon phase on first day of year (epact) ~= (11*(year%19) + 18) % 30
-     *  (18 as initial condition for 1900)
-     *
-     * current phase in days = first day phase + days elapsed in year
-     *
-     * 6 moons ~= 177 days
-     * 177 ~= 8 reported phases * 22
-     * + 11/22 for rounding
-     * 
- * - * @return The phase of the moon as a number between 0 and 7 with - * 0 meaning new moon and 4 meaning full moon. - * - * @since 1.2, Ant 1.5 - */ - public static int getPhaseOfMoon(Calendar cal) { - int dayOfTheYear = cal.get(Calendar.DAY_OF_YEAR); - int yearInMetonicCycle = ((cal.get(Calendar.YEAR) - 1900) % 19) + 1; - int epact = (11 * yearInMetonicCycle + 18) % 30; - if ((epact == 25 && yearInMetonicCycle > 11) || epact == 24) { - epact++; + public static Date round(Object val, int field) { + if (val instanceof Date) { + return round((Date) val, field); + } else if (val instanceof Calendar) { + return round((Calendar) val, field).getTime(); + } else { + throw new ClassCastException("Could not round " + val); } - return (((((dayOfTheYear + epact) * 6) + 11) % 177) / 22) & 7; } /** - * Returns the current Date in a format suitable for a SMTP date - * header. - * - * @since Ant 1.5.2 + * See the other trunc method. Works with a Date. */ - public static String getDateForHeader() { - Calendar cal = Calendar.getInstance(); - TimeZone tz = cal.getTimeZone(); - int offset = tz.getOffset(cal.get(Calendar.ERA), - cal.get(Calendar.YEAR), - cal.get(Calendar.MONTH), - cal.get(Calendar.DAY_OF_MONTH), - cal.get(Calendar.DAY_OF_WEEK), - cal.get(Calendar.MILLISECOND)); - StringBuffer tzMarker = new StringBuffer(offset < 0 ? "-" : "+"); - offset = Math.abs(offset); - int hours = offset / (60 * 60 * 1000); - int minutes = offset / (60 * 1000) - 60 * hours; - if (hours < 10) { - tzMarker.append("0"); - } - tzMarker.append(hours); - if (minutes < 10) { - tzMarker.append("0"); - } - tzMarker.append(minutes); - return DATE_HEADER_FORMAT.format(cal.getTime()) + tzMarker.toString(); + public static Date trunc(Date val, int field) { + GregorianCalendar gval = new GregorianCalendar(); + gval.setTime(val); + modify(gval, field, false); + return gval.getTime(); } + + /** + * Truncate this date, leaving the field specified as the most significant + * field. For example, if you had the datetime of 28 Mar 2002 + * 13:45:01.231, if you passed with HOUR, it would return 28 Mar + * 2002 13:00:00.000. If this was passed with MONTH, it would return + * 1 Mar 2002 0:00:00.000. + */ + public static Calendar trunc(Calendar val, int field) { + Calendar truncated = (Calendar) val.clone(); + modify(truncated, field, false); + return truncated; + } + + /** + * See the other trunc method. Works with an Object, trying to + * use it as either a Date or Calendar. + */ + public static Date trunc(Object val, int field) { + if (val instanceof Date) { + return trunc((Date) val, field); + } else if (val instanceof Calendar) { + return trunc((Calendar) val, field).getTime(); + } else { + throw new ClassCastException("Could not trunc " + val); + } + } + + private static void modify(Calendar val, int field, boolean round) { + boolean roundUp = false; + for (int i = 0; i < fields.length; i++) { + for (int j = 0; j < fields[i].length; j++) { + if (fields[i][j] == field) { + //This is our field... we stop looping + if (round && roundUp) { + if (field == DateUtils.SEMI_MONTH) { + //This is a special case that's hard to generalize + //If the date is 1, we round up to 16, otherwise + // we subtract 15 days and add 1 month + if (val.get(Calendar.DATE) == 1) { + val.add(Calendar.DATE, 15); + } else { + val.add(Calendar.DATE, -15); + val.add(Calendar.MONTH, 1); + } + } else { + //We need at add one to this field since the + // last number causes us to round up + val.add(fields[i][0], 1); + } + } + return; + } + } + //We have various fields that are not easy roundings + int offset = 0; + boolean offsetSet = false; + //These are special types of fields that require different rounding rules + switch (field) { + case DateUtils.SEMI_MONTH: + if (fields[i][0] == Calendar.DATE) { + //If we're going to drop the DATE field's value, + // we want to do this our own way. + //We need to subtrace 1 since the date has a minimum of 1 + offset = val.get(Calendar.DATE) - 1; + //If we're above 15 days adjustment, that means we're in the + // bottom half of the month and should stay accordingly. + if (offset >= 15) { + offset -= 15; + } + //Record whether we're in the top or bottom half of that range + roundUp = offset > 7; + offsetSet = true; + } + break; + case Calendar.AM_PM: + if (fields[i][0] == Calendar.HOUR) { + //If we're going to drop the HOUR field's value, + // we want to do this our own way. + offset = val.get(Calendar.HOUR); + if (offset >= 12) { + offset -= 12; + } + roundUp = offset > 6; + offsetSet = true; + } + break; + } + if (!offsetSet) { + int min = val.getActualMinimum(fields[i][0]); + int max = val.getActualMaximum(fields[i][0]); + //Calculate the offset from the minimum allowed value + offset = val.get(fields[i][0]) - min; + //Set roundUp if this is more than half way between the minimum and maximum + roundUp = offset > ((max - min) / 2); + } + //We need to remove this field + val.add(fields[i][0], -offset); + } + throw new RuntimeException("We do not support that field."); + + } + + /** + * Parses strings the way that CVS supports it (very human readable). + */ + public static Calendar parse(String original) { + return parse(original, Locale.getDefault()); + } + + /** + * Parses strings the way that CVS supports it (very human readable). + */ + public static Calendar parse(String original, Locale locale) { + //Get the symbol names + DateFormatSymbols symbols = new DateFormatSymbols(locale); + + //Prep the string to parse + String value = original.toLowerCase().trim(); + + //Get the current date/time + Calendar now = Calendar.getInstance(); + if (value.endsWith(" ago")) { + //If this was a date that was "ago" the current time... + //Strip out the ' ago' part + value = value.substring(0, value.length() - 4); + + //Split the value and unit + int start = value.indexOf(" "); + if (start < 0) { + throw new RuntimeException("Could not find space in between value and unit"); + } + String unit = value.substring(start + 1); + value = value.substring(0, start); + //We support "a week", so we need to parse the value as "a" + int val = 0; + if (value.equals("a") || value.equals("an")) { + val = 1; + } else { + val = Integer.parseInt(value); + } + + //Determine the unit + if (unit.equals("milliseconds") || unit.equals("millisecond")) { + now.add(Calendar.MILLISECOND, -val); + } else if (unit.equals("seconds") || unit.equals("second")) { + now.add(Calendar.SECOND, -val); + } else if (unit.equals("minutes") || unit.equals("minute")) { + now.add(Calendar.MINUTE, -val); + } else if (unit.equals("hours") || unit.equals("hour")) { + now.add(Calendar.HOUR, -val); + } else if (unit.equals("days") || unit.equals("day")) { + now.add(Calendar.DATE, -val); + } else if (unit.equals("weeks") || unit.equals("week")) { + now.add(Calendar.DATE, -val * 7); + } else if (unit.equals("fortnights") || unit.equals("fortnight")) { + now.add(Calendar.DATE, -val * 14); + } else if (unit.equals("months") || unit.equals("month")) { + now.add(Calendar.MONTH, -val); + } else if (unit.equals("years") || unit.equals("year")) { + now.add(Calendar.YEAR, -val); + } else { + throw new RuntimeException("We do not understand that many units ago"); + } + return now; + } else if (value.startsWith("last ")) { + //If this was the last time a certain field was met + //Strip out the 'last ' part + value = value.substring(5); + //Get the current date/time + String[] strings = symbols.getWeekdays(); + for (int i = 0; i < strings.length; i++) { + if (value.equalsIgnoreCase(strings[i])) { + //How many days after Sunday + int daysAgo = now.get(Calendar.DAY_OF_WEEK) - i; + if (daysAgo <= 0) { + daysAgo += 7; + } + now.add(Calendar.DATE, -daysAgo); + return now; + } + } + strings = symbols.getMonths(); + for (int i = 0; i < strings.length; i++) { + if (value.equalsIgnoreCase(strings[i])) { + //How many days after January + int monthsAgo = now.get(Calendar.MONTH) - i; + if (monthsAgo <= 0) { + monthsAgo += 12; + } + now.add(Calendar.MONTH, -monthsAgo); + return now; + } + } + if (value.equals("week")) { + now.add(Calendar.DATE, -7); + return now; + } + } else if (value.equals("yesterday")) { + now.add(Calendar.DATE, -1); + return now; + } else if (value.equals("tomorrow")) { + now.add(Calendar.DATE, 1); + return now; + } + //Try to parse the date a number of different ways + for (int i = 0; i < dateFormats.length; i++) { + try { + Date datetime = dateFormats[i].parse(original); + Calendar cal = Calendar.getInstance(); + cal.setTime(datetime); + return cal; + } catch (ParseException pe) { + //we ignore this and just keep trying + } + } + + throw new RuntimeException("Unable to parse '" + original + "'."); + } + + /** + * This constructs an Iterator that will start and stop over a date + * range based on the focused date and the range style. For instance, + * passing Thursday, July 4, 2002 and a RANGE_MONTH_SUNDAY will return + * an Iterator that starts with Sunday, June 30, 2002 and ends with + * Saturday, August 3, 2002. + */ + public static Iterator getCalendarIterator(Calendar focus, int rangeStyle) { + Calendar start = null; + Calendar end = null; + int startCutoff = Calendar.SUNDAY; + int endCutoff = Calendar.SATURDAY; + switch (rangeStyle) { + case RANGE_MONTH_SUNDAY: + case RANGE_MONTH_MONDAY: + //Set start to the first of the month + start = trunc(focus, Calendar.MONTH); + //Set end to the last of the month + end = (Calendar) start.clone(); + end.add(Calendar.MONTH, 1); + end.add(Calendar.DATE, -1); + //Loop start back to the previous sunday or monday + if (rangeStyle == RANGE_MONTH_MONDAY) { + startCutoff = Calendar.MONDAY; + endCutoff = Calendar.SUNDAY; + } + break; + case RANGE_WEEK_SUNDAY: + case RANGE_WEEK_MONDAY: + case RANGE_WEEK_RELATIVE: + case RANGE_WEEK_CENTER: + //Set start and end to the current date + start = trunc(focus, Calendar.DATE); + end = trunc(focus, Calendar.DATE); + switch (rangeStyle) { + case RANGE_WEEK_SUNDAY: + //already set by default + break; + case RANGE_WEEK_MONDAY: + startCutoff = Calendar.MONDAY; + endCutoff = Calendar.SUNDAY; + break; + case RANGE_WEEK_RELATIVE: + startCutoff = focus.get(Calendar.DAY_OF_WEEK); + endCutoff = startCutoff - 1; + break; + case RANGE_WEEK_CENTER: + startCutoff = focus.get(Calendar.DAY_OF_WEEK) - 3; + endCutoff = focus.get(Calendar.DAY_OF_WEEK) + 3; + break; + } + break; + default: + throw new RuntimeException("The range style " + rangeStyle + " is not valid."); + } + if (startCutoff < Calendar.SUNDAY) { + startCutoff += 7; + } + if (startCutoff > Calendar.SATURDAY) { + startCutoff -= 7; + } + if (endCutoff < Calendar.SUNDAY) { + endCutoff += 7; + } + if (endCutoff > Calendar.SATURDAY) { + endCutoff -= 7; + } + while (start.get(Calendar.DAY_OF_WEEK) != startCutoff) { + start.add(Calendar.DATE, -1); + } + while (end.get(Calendar.DAY_OF_WEEK) != endCutoff) { + end.add(Calendar.DATE, 1); + } + final Calendar startFinal = start; + final Calendar endFinal = end; + Iterator it = new Iterator() { + Calendar spot = null; + { + spot = startFinal; + spot.add(Calendar.DATE, -1); + } + + public boolean hasNext() { + return spot.before(endFinal); + } + + public Object next() { + if (spot.equals(endFinal)) { + throw new NoSuchElementException(); + } + spot.add(Calendar.DATE, 1); + return spot.clone(); + } + + public void remove() { + throw new UnsupportedOperationException(); + } + }; + return it; + } + + /** + * See the other getCalendarIterator. Works with a Date. + */ + public static Iterator getCalendarIterator(Date focus, int rangeStyle) { + GregorianCalendar gval = new GregorianCalendar(); + gval.setTime(focus); + return getCalendarIterator(gval, rangeStyle); + } + + /** + * See the other getCalendarIterator. Works with an Object, trying + * to use it as a Date or Calendar. + */ + public static Iterator getCalendarIterator(Object focus, int rangeStyle) { + if (focus instanceof Date) { + return getCalendarIterator((Date) focus, rangeStyle); + } else if (focus instanceof Calendar) { + return getCalendarIterator((Calendar) focus, rangeStyle); + } else { + throw new ClassCastException("Could not iterate based on " + focus); + } + } + } diff --git a/src/java/org/apache/commons/lang/time/DurationFormatUtils.java b/src/java/org/apache/commons/lang/time/DurationFormatUtils.java new file mode 100644 index 000000000..c37851498 --- /dev/null +++ b/src/java/org/apache/commons/lang/time/DurationFormatUtils.java @@ -0,0 +1,167 @@ +/* ==================================================================== + * The Apache Software License, Version 1.1 + * + * Copyright (c) 2002-2003 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowlegement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowlegement may appear in the software itself, + * if and wherever such third-party acknowlegements normally appear. + * + * 4. The names "The Jakarta Project", "Commons", and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Software Foundation. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + */ +package org.apache.commons.lang.time; + +/** + * Duration formatting utilites and constants. + * + * @author Apache Ant - DateUtils + * @author Stephane Bailliez + * @author Stefan Bodewig + * @author Stephen Colebourne + * @since 2.0 + * @version $Id: DurationFormatUtils.java,v 1.1 2003/06/08 23:14:23 scolebourne Exp $ + */ +public final class DurationFormatUtils { + + //----------------------------------------------------------------------- + /** + * DurationFormatUtils instances should NOT be constructed in standard programming. + *

+ * This constructor is public to permit tools that require a JavaBean instance + * to operate. + */ + public DurationFormatUtils() { + } + + /** + * Format an elapsed time into a plurialization correct string. + * It is limited only to report elapsed time in minutes and + * seconds and has the following behavior. + *

    + *
  • minutes are not displayed when 0. (ie: "45 seconds")
  • + *
  • seconds are always displayed in plural form (ie "0 seconds" or + * "10 seconds") except for 1 (ie "1 second")
  • + *
+ * + * @param millis the elapsed time to report in milliseconds + * @return the formatted text in minutes/seconds + */ + public static String formatWords(long millis, boolean supressLeadingZeroElements, boolean supressTrailingZeroElements) { + long[] values = new long[4]; + values[0] = millis / DateUtils.MILLIS_IN_DAY; + values[1] = (millis / DateUtils.MILLIS_IN_HOUR) % 24; + values[2] = (millis / DateUtils.MILLIS_IN_MINUTE) % 60; + values[3] = (millis / DateUtils.MILLIS_IN_SECOND) % 60; + String[] fieldsOne = {" day ", " hour ", " minute ", " second"}; + String[] fieldsPlural = {" days ", " hours ", " minutes ", " seconds"}; + + StringBuffer buf = new StringBuffer(64); + boolean valueOutput = false; + + for (int i = 0; i < 4; i++) { + long value = values[i]; + if (value == 0) { + // handle zero + if (valueOutput) { + if (supressTrailingZeroElements == false) { + buf.append('0').append(fieldsPlural[i]); + } + } else { + if (supressLeadingZeroElements == false) { + buf.append('0').append(fieldsPlural[i]); + } + } + } else if (value == 1) { + // one + valueOutput = true; + buf.append('1').append(fieldsOne[i]); + } else { + // other + valueOutput = true; + buf.append(value).append(fieldsPlural[i]); + } + } + + return buf.toString().trim(); + } + + /** + *

Get the time gap as a string.

+ * + *

The format used is ISO8601-like. + * hours:minutes:seconds.milliseconds.

+ * + * @param millis the duration to format + * @return the time as a String + */ + public static String formatISO(long millis) { + int hours, minutes, seconds, milliseconds; + hours = (int) (millis / DateUtils.MILLIS_IN_HOUR); + millis = millis - (hours * DateUtils.MILLIS_IN_HOUR); + minutes = (int) (millis / DateUtils.MILLIS_IN_MINUTE); + millis = millis - (minutes * DateUtils.MILLIS_IN_MINUTE); + seconds = (int) (millis / DateUtils.MILLIS_IN_SECOND); + millis = millis - (seconds * DateUtils.MILLIS_IN_SECOND); + milliseconds = (int) millis; + + StringBuffer buf = new StringBuffer(32); + buf.append(hours); + buf.append(':'); + buf.append((char)(minutes / 10 + '0')); + buf.append((char)(minutes % 10 + '0')); + buf.append(':'); + buf.append((char)(seconds / 10 + '0')); + buf.append((char)(seconds % 10 + '0')); + buf.append('.'); + if (milliseconds < 10) { + buf.append('0').append('0'); + } else if (milliseconds < 100) { + buf.append('0'); + } + buf.append(milliseconds); + return buf.toString(); + } + +} diff --git a/src/java/org/apache/commons/lang/time/FastDateFormat.java b/src/java/org/apache/commons/lang/time/FastDateFormat.java index e93278c2a..ff63b8913 100644 --- a/src/java/org/apache/commons/lang/time/FastDateFormat.java +++ b/src/java/org/apache/commons/lang/time/FastDateFormat.java @@ -53,7 +53,6 @@ */ package org.apache.commons.lang.time; -import java.io.Serializable; import java.text.DateFormat; import java.text.DateFormatSymbols; import java.text.FieldPosition; @@ -71,11 +70,18 @@ import java.util.Map; import java.util.TimeZone; /** - * FastDateFormat is similar to {@link java.text.SimpleDateFormat}, but - * faster and thread-safe. + * FastDateFormat is a fast and thread-safe version of {@link java.text.SimpleDateFormat}. *

* Only formatting is supported, but all patterns are compatible with - * SimpleDateFormat. + * SimpleDateFormat (except timezones - see below). + *

+ * Java 1.4 introduced a new pattern letter, 'Z', to represent time zones in + * RFC822 format (eg. +0800 or -1100). This pattern letter can be used here (on + * all JDK versions). + *

+ * In addition, the pattern 'ZZ' has been made to represent ISO8601 full format + * time zones (eg. +08:00 or -11:00). This introduces a minor incompatability with + * Java 1.4, but at a gain of useful functionality. *

* NOTE: Code originally taken from the open source TreeTrove project. * @@ -84,24 +90,34 @@ import java.util.TimeZone; * @author Gary Gregory * @author Stephen Colebourne * @since 2.0 - * @version $Id: FastDateFormat.java,v 1.5 2003/05/21 23:39:53 scolebourne Exp $ + * @version $Id: FastDateFormat.java,v 1.6 2003/06/08 23:14:23 scolebourne Exp $ */ public class FastDateFormat extends Format { + // A lot of the speed in this class comes from caching, but some comes + // from the special int to StringBuffer conversion. + // + // The following produces a padded 2 digit number: + // buffer.append((char)(value / 10 + '0')); + // buffer.append((char)(value % 10 + '0')); + // + // Note that the fastest append to StringBuffer is a single char (used here). + // Note that Integer.toString() is not called, the conversion is simply + // taking the value and adding (mathematically) the ASCII value for '0'. + // So, don't change this code! It works and is vary fast. - /** FULL date or time style */ + /** FULL locale dependent date or time style */ public static final int FULL = SimpleDateFormat.FULL; - /** LONG date or time style */ + /** LONG locale dependent date or time style */ public static final int LONG = SimpleDateFormat.LONG; - /** MEDIUM date or time style */ + /** MEDIUM locale dependent date or time style */ public static final int MEDIUM = SimpleDateFormat.MEDIUM; - /** SHORT date or time style */ + /** SHORT locale dependent date or time style */ public static final int SHORT = SimpleDateFormat.SHORT; // package scoped as used by inner class static final double LOG_10 = Math.log(10); private static String cDefaultPattern; - private static TimeZone cDefaultTimeZone = TimeZone.getDefault(); private static Map cInstanceCache = new HashMap(7); private static Map cDateInstanceCache = new HashMap(7); @@ -113,12 +129,16 @@ public class FastDateFormat extends Format { private final String mPattern; /** The time zone */ private final TimeZone mTimeZone; + /** Whether the time zone overrides any on Calendars */ + private final boolean mTimeZoneForced; /** The locale */ private final Locale mLocale; + /** Whether the locale overrides the default */ + private final boolean mLocaleForced; /** The parsed rules */ - private final Rule[] mRules; + private Rule[] mRules; /** The estimated maximum length */ - private final int mMaxLengthEstimate; + private int mMaxLengthEstimate; //----------------------------------------------------------------------- /** @@ -172,25 +192,15 @@ public class FastDateFormat extends Format { * @param timeZone optional time zone, overrides time zone of formatted date * @param locale optional locale, overrides system locale * @return a pattern based date/time formatter - * @throws IllegalArgumentException if pattern is invalid + * @throws IllegalArgumentException if pattern is invalid or null */ public static synchronized FastDateFormat getInstance(String pattern, TimeZone timeZone, Locale locale) { - Object key = pattern; - if (timeZone != null) { - key = new Pair(key, timeZone); - } - if (locale != null) { - key = new Pair(key, locale); - } - - FastDateFormat format = (FastDateFormat) cInstanceCache.get(key); + FastDateFormat emptyFormat = new FastDateFormat(pattern, timeZone, locale); + FastDateFormat format = (FastDateFormat) cInstanceCache.get(emptyFormat); if (format == null) { - if (locale == null) { - locale = Locale.getDefault(); - } - - format = new FastDateFormat(pattern, timeZone, locale, new DateFormatSymbols(locale)); - cInstanceCache.put(key, format); + format = emptyFormat; + format.init(); // convert shell format into usable one + cInstanceCache.put(format, format); // this is OK! } return format; } @@ -342,17 +352,62 @@ public class FastDateFormat extends Format { return cDefaultPattern; } + // Constructor + //----------------------------------------------------------------------- + /** + * Constructs a new FastDateFormat. + * + * @param pattern {@link java.text.SimpleDateFormat} compatible pattern + * @param timeZone time zone to use, null means use default for Date and + * value within for Calendar + * @param locale locale, null means use system default + * @throws IllegalArgumentException if pattern is invalid or null + */ + protected FastDateFormat(String pattern, TimeZone timeZone, Locale locale) { + super(); + if (pattern == null) { + throw new IllegalArgumentException("The pattern must not be null"); + } + mPattern = pattern; + + mTimeZoneForced = (timeZone != null); + if (timeZone == null) { + timeZone = TimeZone.getDefault(); + } + mTimeZone = timeZone; + + mLocaleForced = (locale != null); + if (locale == null) { + locale = Locale.getDefault(); + } + mLocale = locale; + } + + /** + * Initialise the instance for first use. + */ + protected void init() { + List rulesList = parsePattern(); + mRules = (Rule[]) rulesList.toArray(new Rule[rulesList.size()]); + + int len = 0; + for (int i=mRules.length; --i >= 0; ) { + len += mRules[i].estimateLength(); + } + + mMaxLengthEstimate = len; + } + + // Parse the pattern + //----------------------------------------------------------------------- /** * Returns a list of Rules given a pattern. * - * @param pattern the pattern to parse - * @param timeZone the time zone to use - * @param locale the locale to use - * @param symbols the symbols to use * @return a List of Rule objects * @throws IllegalArgumentException if pattern is invalid */ - private static List parse(String pattern, TimeZone timeZone, Locale locale, DateFormatSymbols symbols) { + protected List parsePattern() { + DateFormatSymbols symbols = new DateFormatSymbols(mLocale); List rules = new ArrayList(); String[] ERAs = symbols.getEras(); @@ -362,12 +417,12 @@ public class FastDateFormat extends Format { String[] shortWeekdays = symbols.getShortWeekdays(); String[] AmPmStrings = symbols.getAmPmStrings(); - int length = pattern.length(); + int length = mPattern.length(); int[] indexRef = new int[1]; for (int i = 0; i < length; i++) { indexRef[0] = i; - String token = parseToken(pattern, indexRef); + String token = parseToken(mPattern, indexRef); i = indexRef[0]; int tokenLen = token.length(); @@ -384,9 +439,9 @@ public class FastDateFormat extends Format { break; case 'y': // year (number) if (tokenLen >= 4) { - rule = new UnpaddedNumberField(Calendar.YEAR); + rule = UnpaddedNumberField.INSTANCE_YEAR; } else { - rule = new TwoDigitYearField(); + rule = TwoDigitYearField.INSTANCE; } break; case 'M': // month in year (text and number) @@ -395,9 +450,9 @@ public class FastDateFormat extends Format { } else if (tokenLen == 3) { rule = new TextField(Calendar.MONTH, shortMonths); } else if (tokenLen == 2) { - rule = new TwoDigitMonthField(); + rule = TwoDigitMonthField.INSTANCE; } else { - rule = new UnpaddedMonthField(); + rule = UnpaddedMonthField.INSTANCE; } break; case 'd': // day in month (number) @@ -444,9 +499,16 @@ public class FastDateFormat extends Format { break; case 'z': // time zone (text) if (tokenLen >= 4) { - rule = new TimeZoneRule(timeZone, locale, TimeZone.LONG); + rule = new TimeZoneNameRule(mTimeZone, mTimeZoneForced, mLocale, TimeZone.LONG); } else { - rule = new TimeZoneRule(timeZone, locale, TimeZone.SHORT); + rule = new TimeZoneNameRule(mTimeZone, mTimeZoneForced, mLocale, TimeZone.SHORT); + } + break; + case 'Z': // time zone (value) + if (tokenLen == 1) { + rule = TimeZoneNumberRule.INSTANCE_NO_COLON; + } else { + rule = TimeZoneNumberRule.INSTANCE_COLON; } break; case '\'': // literal text @@ -474,7 +536,7 @@ public class FastDateFormat extends Format { * @param indexRef index references * @return parsed token */ - private static String parseToken(String pattern, int[] indexRef) { + protected String parseToken(String pattern, int[] indexRef) { StringBuffer buf = new StringBuffer(); int i = indexRef[0]; @@ -533,7 +595,7 @@ public class FastDateFormat extends Format { * @param padding the padding required * @return a new rule with the correct padding */ - private static NumberRule selectNumberRule(int field, int padding) { + protected NumberRule selectNumberRule(int field, int padding) { switch (padding) { case 1: return new UnpaddedNumberField(field); @@ -544,40 +606,7 @@ public class FastDateFormat extends Format { } } - //----------------------------------------------------------------------- - /** - * Constructs a new FastDateFormat. - * - * @param pattern {@link java.text.SimpleDateFormat} compatible pattern - * @param timeZone optional time zone, overrides time zone of formatted date - * @param locale optional locale, overrides system locale - * @param symbols optional date format symbols, overrides symbols for provided locale - * @throws IllegalArgumentException if pattern is invalid - */ - private FastDateFormat(String pattern, TimeZone timeZone, Locale locale, DateFormatSymbols symbols) { - if (locale == null) { - locale = Locale.getDefault(); - } - - mPattern = pattern; - mTimeZone = timeZone; - mLocale = locale; - - if (symbols == null) { - symbols = new DateFormatSymbols(locale); - } - - List rulesList = parse(pattern, timeZone, locale, symbols); - mRules = (Rule[]) rulesList.toArray(new Rule[rulesList.size()]); - - int len = 0; - for (int i=mRules.length; --i >= 0; ) { - len += mRules[i].estimateLength(); - } - - mMaxLengthEstimate = len; - } - + // Format methods //----------------------------------------------------------------------- /** * Format either a Date or a Calendar object. @@ -605,11 +634,8 @@ public class FastDateFormat extends Format { * @return the formatted string */ public String format(Date date) { - Calendar c = new GregorianCalendar(cDefaultTimeZone); + Calendar c = new GregorianCalendar(mTimeZone); c.setTime(date); - if (mTimeZone != null) { - c.setTimeZone(mTimeZone); - } return applyRules(c, new StringBuffer(mMaxLengthEstimate)).toString(); } @@ -631,11 +657,8 @@ public class FastDateFormat extends Format { * @return the specified string buffer */ public StringBuffer format(Date date, StringBuffer buf) { - Calendar c = new GregorianCalendar(cDefaultTimeZone); + Calendar c = new GregorianCalendar(mTimeZone); c.setTime(date); - if (mTimeZone != null) { - c.setTimeZone(mTimeZone); - } return applyRules(c, buf); } @@ -647,8 +670,8 @@ public class FastDateFormat extends Format { * @return the specified string buffer */ public StringBuffer format(Calendar calendar, StringBuffer buf) { - if (mTimeZone != null) { - calendar = (Calendar)calendar.clone(); + if (mTimeZoneForced) { + calendar = (Calendar) calendar.clone(); calendar.setTimeZone(mTimeZone); } return applyRules(calendar, buf); @@ -661,7 +684,7 @@ public class FastDateFormat extends Format { * @param buf the buffer to format into * @return the specified string buffer */ - private StringBuffer applyRules(Calendar calendar, StringBuffer buf) { + protected StringBuffer applyRules(Calendar calendar, StringBuffer buf) { Rule[] rules = mRules; int len = mRules.length; for (int i = 0; i < len; i++) { @@ -670,6 +693,7 @@ public class FastDateFormat extends Format { return buf; } + // Parsing //----------------------------------------------------------------------- /** * Parsing not supported. @@ -684,6 +708,7 @@ public class FastDateFormat extends Format { return null; } + // Accessors //----------------------------------------------------------------------- /** * Gets the pattern used by this formatter. @@ -695,8 +720,11 @@ public class FastDateFormat extends Format { } /** - * Gets the time zone used by this formatter, or null if time zone of - * formatted dates is used instead. + * Gets the time zone used by this formatter. + *

+ * This zone is always used for Date formatting. + * If a Calendar is passed in to be formatted, the time zone on that may + * be used depending on {@link #getTimeZoneOverridesCalendar()}. * * @return the time zone */ @@ -704,6 +732,16 @@ public class FastDateFormat extends Format { return mTimeZone; } + /** + * Returns true if the time zone of the calendar overrides the time zone + * of the formatter + * + * @return true if time zone of formatter overridden for calendars + */ + public boolean getTimeZoneOverridesCalendar() { + return mTimeZoneForced; + } + /** * Gets the locale used by this formatter. * @@ -723,6 +761,56 @@ public class FastDateFormat extends Format { return mMaxLengthEstimate; } + // Basics + //----------------------------------------------------------------------- + /** + * Compare two objects for equality. + * + * @param obj the object to compare to + * @return true if equal + */ + public boolean equals(Object obj) { + if (obj instanceof FastDateFormat == false) { + return false; + } + FastDateFormat other = (FastDateFormat) obj; + if ( + (mPattern == other.mPattern || mPattern.equals(other.mPattern)) && + (mTimeZone == other.mTimeZone || mTimeZone.equals(other.mTimeZone)) && + (mLocale == other.mLocale || mLocale.equals(other.mLocale)) && + (mTimeZoneForced == other.mTimeZoneForced) && + (mLocaleForced == other.mLocaleForced) + ) { + return true; + } + return false; + } + + /** + * A suitable hashcode. + * + * @return a hashcode compatable with equals + */ + public int hashCode() { + int total = 0; + total += mPattern.hashCode(); + total += mTimeZone.hashCode(); + total += (mTimeZoneForced ? 1 : 0); + total += mLocale.hashCode(); + total += (mLocaleForced ? 1 : 0); + return total; + } + + /** + * Gets a debugging string version of this formatter. + * + * @return a debugging string + */ + public String toString() { + return "FastDateFormat[" + mPattern + "]"; + } + + // Rules //----------------------------------------------------------------------- /** * Inner class defining a rule. @@ -809,6 +897,8 @@ public class FastDateFormat extends Format { * Inner class to output an unpadded number. */ private static class UnpaddedNumberField implements NumberRule { + static final UnpaddedNumberField INSTANCE_YEAR = new UnpaddedNumberField(Calendar.YEAR); + private final int mField; UnpaddedNumberField(int field) { @@ -826,12 +916,10 @@ public class FastDateFormat extends Format { public final void appendTo(StringBuffer buffer, int value) { if (value < 10) { buffer.append((char)(value + '0')); - } - else if (value < 100) { + } else if (value < 100) { buffer.append((char)(value / 10 + '0')); buffer.append((char)(value % 10 + '0')); - } - else { + } else { buffer.append(Integer.toString(value)); } } @@ -841,6 +929,8 @@ public class FastDateFormat extends Format { * Inner class to output an unpadded month. */ private static class UnpaddedMonthField implements NumberRule { + static final UnpaddedMonthField INSTANCE = new UnpaddedMonthField(); + UnpaddedMonthField() { } @@ -855,8 +945,7 @@ public class FastDateFormat extends Format { public final void appendTo(StringBuffer buffer, int value) { if (value < 10) { buffer.append((char)(value + '0')); - } - else { + } else { buffer.append((char)(value / 10 + '0')); buffer.append((char)(value % 10 + '0')); } @@ -894,13 +983,11 @@ public class FastDateFormat extends Format { } buffer.append((char)(value / 10 + '0')); buffer.append((char)(value % 10 + '0')); - } - else { + } else { int digits; if (value < 1000) { digits = 3; - } - else { + } else { digits = (int)(Math.log(value) / LOG_10) + 1; } for (int i = mSize; --i >= digits; ) { @@ -933,8 +1020,7 @@ public class FastDateFormat extends Format { if (value < 100) { buffer.append((char)(value / 10 + '0')); buffer.append((char)(value % 10 + '0')); - } - else { + } else { buffer.append(Integer.toString(value)); } } @@ -944,6 +1030,8 @@ public class FastDateFormat extends Format { * Inner class to output a two digit year. */ private static class TwoDigitYearField implements NumberRule { + static final TwoDigitYearField INSTANCE = new TwoDigitYearField(); + TwoDigitYearField() { } @@ -965,6 +1053,8 @@ public class FastDateFormat extends Format { * Inner class to output a two digit month. */ private static class TwoDigitMonthField implements NumberRule { + static final TwoDigitMonthField INSTANCE = new TwoDigitMonthField(); + TwoDigitMonthField() { } @@ -1037,71 +1127,101 @@ public class FastDateFormat extends Format { } /** - * Inner class to output a time zone. + * Inner class to output a time zone name. */ - private static class TimeZoneRule implements Rule { + private static class TimeZoneNameRule implements Rule { private final TimeZone mTimeZone; + private final boolean mTimeZoneForced; private final Locale mLocale; private final int mStyle; private final String mStandard; private final String mDaylight; - TimeZoneRule(TimeZone timeZone, Locale locale, int style) { + TimeZoneNameRule(TimeZone timeZone, boolean timeZoneForced, Locale locale, int style) { mTimeZone = timeZone; + mTimeZoneForced = timeZoneForced; mLocale = locale; mStyle = style; - if (timeZone != null) { + if (timeZoneForced) { mStandard = getTimeZoneDisplay(timeZone, false, style, locale); mDaylight = getTimeZoneDisplay(timeZone, true, style, locale); - } - else { + } else { mStandard = null; mDaylight = null; } } public int estimateLength() { - if (mTimeZone != null) { + if (mTimeZoneForced) { return Math.max(mStandard.length(), mDaylight.length()); - } - else if (mStyle == TimeZone.SHORT) { + } else if (mStyle == TimeZone.SHORT) { return 4; - } - else { + } else { return 40; } } public void appendTo(StringBuffer buffer, Calendar calendar) { - TimeZone timeZone; - if ((timeZone = mTimeZone) != null) { - if (timeZone.useDaylightTime() && - calendar.get(Calendar.DST_OFFSET) != 0) { - + if (mTimeZoneForced) { + if (mTimeZone.useDaylightTime() && calendar.get(Calendar.DST_OFFSET) != 0) { buffer.append(mDaylight); - } - else { + } else { buffer.append(mStandard); } - } - else { - timeZone = calendar.getTimeZone(); - if (timeZone.useDaylightTime() && - calendar.get(Calendar.DST_OFFSET) != 0) { - - buffer.append(getTimeZoneDisplay - (timeZone, true, mStyle, mLocale)); - } - else { - buffer.append(getTimeZoneDisplay - (timeZone, false, mStyle, mLocale)); + } else { + TimeZone timeZone = calendar.getTimeZone(); + if (timeZone.useDaylightTime() && calendar.get(Calendar.DST_OFFSET) != 0) { + buffer.append(getTimeZoneDisplay(timeZone, true, mStyle, mLocale)); + } else { + buffer.append(getTimeZoneDisplay(timeZone, false, mStyle, mLocale)); } } } } - // ---------------------------------------------------------------------------------- + /** + * Inner class to output a time zone as a number +/-HHMM or +/-HH:MM. + */ + private static class TimeZoneNumberRule implements Rule { + static final TimeZoneNumberRule INSTANCE_COLON = new TimeZoneNumberRule(true); + static final TimeZoneNumberRule INSTANCE_NO_COLON = new TimeZoneNumberRule(false); + + final boolean mColon; + + TimeZoneNumberRule(boolean colon) { + mColon = colon; + } + + public int estimateLength() { + return 5; + } + + public void appendTo(StringBuffer buffer, Calendar calendar) { + int offset = calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET); + + if (offset < 0) { + buffer.append('-'); + offset = -offset; + } else { + buffer.append('+'); + } + + int hours = offset / (60 * 60 * 1000); + buffer.append((char)(hours / 10 + '0')); + buffer.append((char)(hours % 10 + '0')); + + if (mColon) { + buffer.append(':'); + } + + int minutes = offset / (60 * 1000) - 60 * hours; + buffer.append((char)(minutes / 10 + '0')); + buffer.append((char)(minutes % 10 + '0')); + } + } + + // ---------------------------------------------------------------------- /** * Inner class that acts as a compound key for time zone names. */ @@ -1139,12 +1259,12 @@ public class FastDateFormat extends Format { } } - // ---------------------------------------------------------------------------------- + // ---------------------------------------------------------------------- /** * Helper class for creating compound objects. One use for this class is to create a * hashtable key out of multiple objects. */ - private static class Pair implements Comparable, Serializable { + private static class Pair { private final Object mObj1; private final Object mObj2; @@ -1153,56 +1273,6 @@ public class FastDateFormat extends Format { mObj2 = obj2; } - public int compareTo(Object obj) { - if (this == obj) { - return 0; - } - - Pair other = (Pair)obj; - - Object a = mObj1; - Object b = other.mObj1; - - firstTest: { - if (a == null) { - if (b != null) { - return 1; - } - // Both a and b are null. - break firstTest; - } - else { - if (b == null) { - return -1; - } - } - - int result = ((Comparable)a).compareTo(b); - - if (result != 0) { - return result; - } - } - - a = mObj2; - b = other.mObj2; - - if (a == null) { - if (b != null) { - return 1; - } - // Both a and b are null. - return 0; - } - else { - if (b == null) { - return -1; - } - } - - return ((Comparable)a).compareTo(b); - } - public boolean equals(Object obj) { if (this == obj) { return true; diff --git a/src/java/org/apache/commons/lang/time/StopWatch.java b/src/java/org/apache/commons/lang/time/StopWatch.java index df6d61c94..793e49bbd 100644 --- a/src/java/org/apache/commons/lang/time/StopWatch.java +++ b/src/java/org/apache/commons/lang/time/StopWatch.java @@ -1,7 +1,7 @@ /* ==================================================================== * The Apache Software License, Version 1.1 * - * Copyright (c) 1999-2003 The Apache Software Foundation. All rights + * Copyright (c) 2002-2003 The Apache Software Foundation. All rights * reserved. * * Redistribution and use in source and binary forms, with or without @@ -77,13 +77,10 @@ package org.apache.commons.lang.time; * @author Henri Yandell * @author Stephen Colebourne * @since 2.0 - * @version $Id: StopWatch.java,v 1.3 2003/05/21 23:37:20 scolebourne Exp $ + * @version $Id: StopWatch.java,v 1.4 2003/06/08 23:14:23 scolebourne Exp $ */ public class StopWatch { - private static final int MILLIS_IN_HOUR = 60 * 60 * 1000; - private static final int MILLIS_IN_MINUTE = 60 * 1000; - /** The start time */ private long startTime = -1; /** The stop time */ @@ -187,53 +184,13 @@ public class StopWatch { /** *

Gets a summary of the time that the stopwatch recorded as a string.

* - *

The format used is ISO8601, + *

The format used is ISO8601-like, * hours:minutes:seconds.milliseconds.

* * @return the time as a String */ public String toString() { - return StopWatch.toString(getTime()); - } - - /** - *

Get the time gap as a string.

- * - *

The format used is ISO8601, - * hours:minutes:seconds.milliseconds.

- * - * @return the time as a String - */ - public static String toString(long time) { - int hours, minutes, seconds, milliseconds; - hours = (int) (time / MILLIS_IN_HOUR); - time = time - (hours * MILLIS_IN_HOUR); - minutes = (int) (time / MILLIS_IN_MINUTE); - time = time - (minutes * MILLIS_IN_MINUTE); - seconds = (int) (time / 1000); - time = time - (seconds * 1000); - milliseconds = (int) time; - - StringBuffer buf = new StringBuffer(32); - buf.append(hours); - buf.append(':'); - if (minutes < 10) { - buf.append('0'); - } - buf.append(minutes); - buf.append(':'); - if (seconds < 10) { - buf.append('0'); - } - buf.append(seconds); - buf.append('.'); - if (milliseconds < 10) { - buf.append("00"); - } else if (milliseconds < 100) { - buf.append('0'); - } - buf.append(milliseconds); - return buf.toString(); + return DurationFormatUtils.formatISO(getTime()); } } diff --git a/src/test/org/apache/commons/lang/time/CalendarUtilsTest.java b/src/test/org/apache/commons/lang/time/CalendarUtilsTest.java deleted file mode 100644 index abfb435ef..000000000 --- a/src/test/org/apache/commons/lang/time/CalendarUtilsTest.java +++ /dev/null @@ -1,379 +0,0 @@ -/* ==================================================================== - * The Apache Software License, Version 1.1 - * - * Copyright (c) 2002-2003 The Apache Software Foundation. All rights - * reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * 3. The end-user documentation included with the redistribution, if - * any, must include the following acknowlegement: - * "This product includes software developed by the - * Apache Software Foundation (http://www.apache.org/)." - * Alternately, this acknowlegement may appear in the software itself, - * if and wherever such third-party acknowlegements normally appear. - * - * 4. The names "The Jakarta Project", "Commons", and "Apache Software - * Foundation" must not be used to endorse or promote products derived - * from this software without prior written permission. For written - * permission, please contact apache@apache.org. - * - * 5. Products derived from this software may not be called "Apache" - * nor may "Apache" appear in their names without prior written - * permission of the Apache Software Foundation. - * - * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR - * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF - * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - * ==================================================================== - * - * This software consists of voluntary contributions made by many - * individuals on behalf of the Apache Software Foundation. For more - * information on the Apache Software Foundation, please see - * . - */ -package org.apache.commons.lang.time; - -import java.text.DateFormat; -import java.util.Calendar; -import java.util.Date; -import java.util.Iterator; - -import junit.framework.AssertionFailedError; -import junit.framework.Test; -import junit.framework.TestCase; -import junit.framework.TestSuite; -import junit.textui.TestRunner; - -/** - * Unit tests {@link org.apache.commons.lang.CalendarUtils}. - * - * @author Serge Knystautas - */ -public class CalendarUtilsTest extends TestCase { - DateFormat parser = null; - Date date1 = null; - Date date2 = null; - - public CalendarUtilsTest(String name) { - super(name); - } - - public static void main(String[] args) { - TestRunner.run(suite()); - } - - public static Test suite() { - TestSuite suite = new TestSuite(CalendarUtilsTest.class); - suite.setName("CalendarUtilsTest Tests"); - return suite; - } - - protected void setUp() throws Exception { - super.setUp(); - - parser = new java.text.SimpleDateFormat("MMM dd, yyyy H:mm:ss.SSS"); - - date1 = parser.parse("February 12, 2002 12:34:56.789"); - date2 = parser.parse("November 18, 2001 1:23:11.321"); - } - - protected void tearDown() throws Exception { - super.tearDown(); - } - - //----------------------------------------------------------------------- - - - /** - * Tests various values with the round method - */ - public void testRound() throws Exception { - assertEquals("round year-1 failed", - new Date("2002 January 1"), - CalendarUtils.round(date1, Calendar.YEAR)); - assertEquals("round year-2 failed", - new Date("2002 January 1"), - CalendarUtils.round(date2, Calendar.YEAR)); - assertEquals("round month-1 failed", - new Date("2002 February 1"), - CalendarUtils.round(date1, Calendar.MONTH)); - assertEquals("round month-2 failed", - new Date("2001 December 1"), - CalendarUtils.round(date2, Calendar.MONTH)); - assertEquals("round semimonth-1 failed", - new Date("2002 February 16"), - CalendarUtils.round(date1, CalendarUtils.SEMI_MONTH)); - assertEquals("round semimonth-2 failed", - new Date("2001 November 16"), - CalendarUtils.round(date2, CalendarUtils.SEMI_MONTH)); - assertEquals("round date-1 failed", - new Date("2002 February 13"), - CalendarUtils.round(date1, Calendar.DATE)); - assertEquals("round date-2 failed", - new Date("2001 November 18"), - CalendarUtils.round(date2, Calendar.DATE)); - assertEquals("round hour-1 failed", - parser.parse("February 12, 2002 13:00:00.000"), - CalendarUtils.round(date1, Calendar.HOUR)); - assertEquals("round hour-2 failed", - parser.parse("November 18, 2001 1:00:00.000"), - CalendarUtils.round(date2, Calendar.HOUR)); - assertEquals("round minute-1 failed", - parser.parse("February 12, 2002 12:35:00.000"), - CalendarUtils.round(date1, Calendar.MINUTE)); - assertEquals("round minute-2 failed", - parser.parse("November 18, 2001 1:23:00.000"), - CalendarUtils.round(date2, Calendar.MINUTE)); - assertEquals("round second-1 failed", - parser.parse("February 12, 2002 12:34:57.000"), - CalendarUtils.round(date1, Calendar.SECOND)); - assertEquals("round second-2 failed", - parser.parse("November 18, 2001 1:23:11.000"), - CalendarUtils.round(date2, Calendar.SECOND)); - } - - /** - * Tests various values with the trunc method - */ - public void testTrunc() throws Exception { - assertEquals("trunc year-1 failed", - new Date("2002 January 1"), - CalendarUtils.trunc(date1, Calendar.YEAR)); - assertEquals("trunc year-2 failed", - new Date("2001 January 1"), - CalendarUtils.trunc(date2, Calendar.YEAR)); - assertEquals("trunc month-1 failed", - new Date("2002 February 1"), - CalendarUtils.trunc(date1, Calendar.MONTH)); - assertEquals("trunc month-2 failed", - new Date("2001 November 1"), - CalendarUtils.trunc(date2, Calendar.MONTH)); - assertEquals("trunc semimonth-1 failed", - new Date("2002 February 1"), - CalendarUtils.trunc(date1, CalendarUtils.SEMI_MONTH)); - assertEquals("trunc semimonth-2 failed", - new Date("2001 November 16"), - CalendarUtils.trunc(date2, CalendarUtils.SEMI_MONTH)); - assertEquals("trunc date-1 failed", - new Date("2002 February 12"), - CalendarUtils.trunc(date1, Calendar.DATE)); - assertEquals("trunc date-2 failed", - new Date("2001 November 18"), - CalendarUtils.trunc(date2, Calendar.DATE)); - assertEquals("trunc hour-1 failed", - parser.parse("February 12, 2002 12:00:00.000"), - CalendarUtils.trunc(date1, Calendar.HOUR)); - assertEquals("trunc hour-2 failed", - parser.parse("November 18, 2001 1:00:00.000"), - CalendarUtils.trunc(date2, Calendar.HOUR)); - assertEquals("trunc minute-1 failed", - parser.parse("February 12, 2002 12:34:00.000"), - CalendarUtils.trunc(date1, Calendar.MINUTE)); - assertEquals("trunc minute-2 failed", - parser.parse("November 18, 2001 1:23:00.000"), - CalendarUtils.trunc(date2, Calendar.MINUTE)); - assertEquals("trunc second-1 failed", - parser.parse("February 12, 2002 12:34:56.000"), - CalendarUtils.trunc(date1, Calendar.SECOND)); - assertEquals("trunc second-2 failed", - parser.parse("November 18, 2001 1:23:11.000"), - CalendarUtils.trunc(date2, Calendar.SECOND)); - - } - - /** - * Tests the parse method, which is supposed to handle various strings - * as flexibly as CVS supports. - */ - public void testParse() throws Exception { - //This is difficult to test since the "now" used in the - // parse function cannot be controlled. We could possibly control - // it by trying before and after and making sure the value we expect - // is between the two values calculated. - //For now we're just using the custom assertEquals that takes a delta - - Calendar now = null; - - now = Calendar.getInstance(); - now.add(Calendar.MINUTE, -1); - assertEquals("parse 1 minute ago", - now, CalendarUtils.parse("1 minute ago"), 50); - now = Calendar.getInstance(); - now.add(Calendar.MINUTE, -8); - assertEquals("parse 8 minutes ago", - now, CalendarUtils.parse("8 minutes ago"), 50); - - now = Calendar.getInstance(); - now.add(Calendar.DATE, -1); - assertEquals("parse yesterday", - now, CalendarUtils.parse("yesterday"), 50); - - now = Calendar.getInstance(); - now.add(Calendar.DATE, 1); - assertEquals("parse tomorrow", - now, CalendarUtils.parse("tomorrow"), 50); - - now = Calendar.getInstance(); - //Sunday would be 1, Saturday would be 7, so we walk back up to 6 days. - if (now.get(Calendar.DATE) == 1) { - //If Sunday already, we go back a full week - now.add(Calendar.DATE, -7); - } else { - now.add(Calendar.DATE, 1 - now.get(Calendar.DAY_OF_WEEK)); - } - assertEquals("parse last Sunday", - now, CalendarUtils.parse("last Sunday"), 50); - - now = Calendar.getInstance(); - now.add(Calendar.DATE, -7); - assertEquals("parse last week", - now, CalendarUtils.parse("last week"), 50); - - now = Calendar.getInstance(); - //January would be 0, December would be 11, so we walk back up to 11 months - if (now.get(Calendar.MONTH) == 0) { - //If January already, we go back a full year - now.add(Calendar.MONTH, -12); - } else { - now.add(Calendar.MONTH, 0 - now.get(Calendar.MONTH)); - } - assertEquals("parse last January", - now, CalendarUtils.parse("last January"), 50); - } - - /** - * Tests the calendar iterator for week ranges - */ - public void testWeekIterator() throws Exception { - Calendar now = Calendar.getInstance(); - Calendar today = CalendarUtils.trunc(now, Calendar.DATE); - Calendar sunday = CalendarUtils.trunc(now, Calendar.DATE); - sunday.add(Calendar.DATE, 1 - sunday.get(Calendar.DAY_OF_WEEK)); - Calendar monday = CalendarUtils.trunc(now, Calendar.DATE); - if (monday.get(Calendar.DATE) == 1) { - //This is sunday... roll back 6 days - monday.add(Calendar.DATE, -6); - } else { - monday.add(Calendar.DATE, 2 - monday.get(Calendar.DAY_OF_WEEK)); - } - Calendar centered = CalendarUtils.trunc(now, Calendar.DATE); - centered.add(Calendar.DATE, -3); - - Iterator it = CalendarUtils.getCalendarIterator(now, CalendarUtils.RANGE_WEEK_SUNDAY); - assertWeekIterator(it, sunday); - it = CalendarUtils.getCalendarIterator(now, CalendarUtils.RANGE_WEEK_MONDAY); - assertWeekIterator(it, monday); - it = CalendarUtils.getCalendarIterator(now, CalendarUtils.RANGE_WEEK_RELATIVE); - assertWeekIterator(it, today); - it = CalendarUtils.getCalendarIterator(now, CalendarUtils.RANGE_WEEK_CENTER); - assertWeekIterator(it, centered); - } - - /** - * Tests the calendar iterator for month-based ranges - */ - public void testMonthIterator() throws Exception { - Iterator it = CalendarUtils.getCalendarIterator(date1, CalendarUtils.RANGE_MONTH_SUNDAY); - assertWeekIterator(it, - new Date("January 27, 2002"), - new Date("March 2, 2002")); - - it = CalendarUtils.getCalendarIterator(date1, CalendarUtils.RANGE_MONTH_MONDAY); - assertWeekIterator(it, - new Date("January 28, 2002"), - new Date("March 3, 2002")); - - it = CalendarUtils.getCalendarIterator(date2, CalendarUtils.RANGE_MONTH_SUNDAY); - assertWeekIterator(it, - new Date("October 28, 2001"), - new Date("December 1, 2001")); - - it = CalendarUtils.getCalendarIterator(date2, CalendarUtils.RANGE_MONTH_MONDAY); - assertWeekIterator(it, - new Date("October 29, 2001"), - new Date("December 2, 2001")); - } - - /** - * This checks that this is a 7 element iterator of Calendar objects - * that are dates (no time), and exactly 1 day spaced after each other. - */ - private static void assertWeekIterator(Iterator it, Calendar start) { - Calendar end = (Calendar) start.clone(); - end.add(Calendar.DATE, 6); - - assertWeekIterator(it, start, end); - } - - /** - * Convenience method for when working with Date objects - */ - private static void assertWeekIterator(Iterator it, Date start, Date end) { - Calendar calStart = Calendar.getInstance(); - calStart.setTime(start); - Calendar calEnd = Calendar.getInstance(); - calEnd.setTime(end); - - assertWeekIterator(it, calStart, calEnd); - } - - /** - * This checks that this is a 7 divisble iterator of Calendar objects - * that are dates (no time), and exactly 1 day spaced after each other - * (in addition to the proper start and stop dates) - */ - private static void assertWeekIterator(Iterator it, Calendar start, Calendar end) { - Calendar cal = (Calendar) it.next(); - assertEquals("", start, cal, 0); - Calendar last = null; - int count = 1; - while (it.hasNext()) { - //Check this is just a date (no time component) - assertEquals("", cal, CalendarUtils.trunc(cal, Calendar.DATE), 0); - - last = cal; - cal = (Calendar) it.next(); - count++; - - //Check that this is one day more than the last date - last.add(Calendar.DATE, 1); - assertEquals("", last, cal, 0); - } - if (count % 7 != 0) { - throw new AssertionFailedError("There were " + count + " days in this iterator"); - } - assertEquals("", end, cal, 0); - } - - /** - * Used to check that Calendar objects are close enough - * delta is in milliseconds - */ - public static void assertEquals(String message, Calendar cal1, Calendar cal2, long delta) { - if (Math.abs(cal1.getTime().getTime() - cal2.getTime().getTime()) > delta) { - throw new AssertionFailedError( - message + " expected " + cal1.getTime() + " but got " + cal2.getTime()); - } - } -} - diff --git a/src/test/org/apache/commons/lang/time/DateFormatUtilsTest.java b/src/test/org/apache/commons/lang/time/DateFormatUtilsTest.java new file mode 100644 index 000000000..bd83416fa --- /dev/null +++ b/src/test/org/apache/commons/lang/time/DateFormatUtilsTest.java @@ -0,0 +1,199 @@ +/* ==================================================================== + * The Apache Software License, Version 1.1 + * + * Copyright (c) 2002-2003 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowlegement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowlegement may appear in the software itself, + * if and wherever such third-party acknowlegements normally appear. + * + * 4. The names "The Jakarta Project", "Commons", and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Software Foundation. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + */ +package org.apache.commons.lang.time; + +import java.util.Calendar; +import java.util.TimeZone; + +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestSuite; +import junit.textui.TestRunner; + +/** + * TestCase for DateFormatUtils. + * + * @author Apache Ant - DateUtilsTest + * @author Stephane Bailliez + * @author Stefan Bodewig + * @author Stephen Colebourne + */ +public class DateFormatUtilsTest extends TestCase { + + public static void main(String[] args) { + TestRunner.run(suite()); + } + + public static Test suite() { + TestSuite suite = new TestSuite(DateFormatUtilsTest.class); + suite.setName("DateFormatUtils Tests"); + return suite; + } + + public DateFormatUtilsTest(String s) { + super(s); + } + + public void testDateTimeISO(){ + TimeZone timeZone = TimeZone.getTimeZone("GMT-3"); + Calendar cal = Calendar.getInstance(timeZone); + cal.set(2002,1,23,9,11,12); + String text = DateFormatUtils.format(cal.getTime(), + DateFormatUtils.ISO_DATETIME_FORMAT.getPattern(), timeZone); + assertEquals("2002-02-23T09:11:12", text); + text = DateFormatUtils.format(cal.getTime().getTime(), + DateFormatUtils.ISO_DATETIME_FORMAT.getPattern(), timeZone); + assertEquals("2002-02-23T09:11:12", text); + text = DateFormatUtils.ISO_DATETIME_FORMAT.format(cal); + assertEquals("2002-02-23T09:11:12", text); + + text = DateFormatUtils.format(cal.getTime(), + DateFormatUtils.ISO_DATETIME_TIMEZONE_FORMAT.getPattern(), timeZone); + assertEquals("2002-02-23T09:11:12-03:00", text); + text = DateFormatUtils.format(cal.getTime().getTime(), + DateFormatUtils.ISO_DATETIME_TIMEZONE_FORMAT.getPattern(), timeZone); + assertEquals("2002-02-23T09:11:12-03:00", text); + text = DateFormatUtils.ISO_DATETIME_TIMEZONE_FORMAT.format(cal); + assertEquals("2002-02-23T09:11:12-03:00", text); + } + + public void testDateISO(){ + TimeZone timeZone = TimeZone.getTimeZone("GMT-3"); + Calendar cal = Calendar.getInstance(timeZone); + cal.set(2002,1,23,10,11,12); + String text = DateFormatUtils.format(cal.getTime(), + DateFormatUtils.ISO_DATE_FORMAT.getPattern(), timeZone); + assertEquals("2002-02-23", text); + text = DateFormatUtils.format(cal.getTime().getTime(), + DateFormatUtils.ISO_DATE_FORMAT.getPattern(), timeZone); + assertEquals("2002-02-23", text); + text = DateFormatUtils.ISO_DATE_FORMAT.format(cal); + assertEquals("2002-02-23", text); + + text = DateFormatUtils.format(cal.getTime(), + DateFormatUtils.ISO_DATE_TIMEZONE_FORMAT.getPattern(), timeZone); + assertEquals("2002-02-23-03:00", text); + text = DateFormatUtils.format(cal.getTime().getTime(), + DateFormatUtils.ISO_DATE_TIMEZONE_FORMAT.getPattern(), timeZone); + assertEquals("2002-02-23-03:00", text); + text = DateFormatUtils.ISO_DATE_TIMEZONE_FORMAT.format(cal); + assertEquals("2002-02-23-03:00", text); + } + + public void testTimeISO(){ + TimeZone timeZone = TimeZone.getTimeZone("GMT-3"); + Calendar cal = Calendar.getInstance(timeZone); + cal.set(2002,1,23,10,11,12); + String text = DateFormatUtils.format(cal.getTime(), + DateFormatUtils.ISO_TIME_FORMAT.getPattern(), timeZone); + assertEquals("T10:11:12", text); + text = DateFormatUtils.format(cal.getTime().getTime(), + DateFormatUtils.ISO_TIME_FORMAT.getPattern(), timeZone); + assertEquals("T10:11:12", text); + text = DateFormatUtils.ISO_TIME_FORMAT.format(cal); + assertEquals("T10:11:12", text); + + text = DateFormatUtils.format(cal.getTime(), + DateFormatUtils.ISO_TIME_TIMEZONE_FORMAT.getPattern(), timeZone); + assertEquals("T10:11:12-03:00", text); + text = DateFormatUtils.format(cal.getTime().getTime(), + DateFormatUtils.ISO_TIME_TIMEZONE_FORMAT.getPattern(), timeZone); + assertEquals("T10:11:12-03:00", text); + text = DateFormatUtils.ISO_TIME_TIMEZONE_FORMAT.format(cal); + assertEquals("T10:11:12-03:00", text); + } + + public void testTimeNoTISO(){ + TimeZone timeZone = TimeZone.getTimeZone("GMT-3"); + Calendar cal = Calendar.getInstance(timeZone); + cal.set(2002,1,23,10,11,12); + String text = DateFormatUtils.format(cal.getTime(), + DateFormatUtils.ISO_TIME_NO_T_FORMAT.getPattern(), timeZone); + assertEquals("10:11:12", text); + text = DateFormatUtils.format(cal.getTime().getTime(), + DateFormatUtils.ISO_TIME_NO_T_FORMAT.getPattern(), timeZone); + assertEquals("10:11:12", text); + text = DateFormatUtils.ISO_TIME_NO_T_FORMAT.format(cal); + assertEquals("10:11:12", text); + + text = DateFormatUtils.format(cal.getTime(), + DateFormatUtils.ISO_TIME_NO_T_TIMEZONE_FORMAT.getPattern(), timeZone); + assertEquals("10:11:12-03:00", text); + text = DateFormatUtils.format(cal.getTime().getTime(), + DateFormatUtils.ISO_TIME_NO_T_TIMEZONE_FORMAT.getPattern(), timeZone); + assertEquals("10:11:12-03:00", text); + text = DateFormatUtils.ISO_TIME_NO_T_TIMEZONE_FORMAT.format(cal); + assertEquals("10:11:12-03:00", text); + } + + public void testSMTP(){ + TimeZone timeZone = TimeZone.getTimeZone("GMT-3"); + Calendar cal = Calendar.getInstance(timeZone); + cal.set(2003,5,8,10,11,12); + String text = DateFormatUtils.format(cal.getTime(), + DateFormatUtils.SMTP_DATETIME_FORMAT.getPattern(), timeZone); + assertEquals("Sun, 08 Jun 2003 10:11:12 -0300", text); + text = DateFormatUtils.format(cal.getTime().getTime(), + DateFormatUtils.SMTP_DATETIME_FORMAT.getPattern(), timeZone); + assertEquals("Sun, 08 Jun 2003 10:11:12 -0300", text); + text = DateFormatUtils.SMTP_DATETIME_FORMAT.format(cal); + assertEquals("Sun, 08 Jun 2003 10:11:12 -0300", text); + + // format UTC + text = DateFormatUtils.formatUTC(cal.getTime().getTime(), + DateFormatUtils.SMTP_DATETIME_FORMAT.getPattern()); + assertEquals("Sun, 08 Jun 2003 13:11:12 +0000", text); + } + +} diff --git a/src/test/org/apache/commons/lang/time/DateUtilsTest.java b/src/test/org/apache/commons/lang/time/DateUtilsTest.java index d31a30a59..43a2dd10f 100644 --- a/src/test/org/apache/commons/lang/time/DateUtilsTest.java +++ b/src/test/org/apache/commons/lang/time/DateUtilsTest.java @@ -1,7 +1,7 @@ -/* +/* ==================================================================== * The Apache Software License, Version 1.1 * - * Copyright (c) 2002 The Apache Software Foundation. All rights + * Copyright (c) 2002-2003 The Apache Software Foundation. All rights * reserved. * * Redistribution and use in source and binary forms, with or without @@ -23,14 +23,14 @@ * Alternately, this acknowlegement may appear in the software itself, * if and wherever such third-party acknowlegements normally appear. * - * 4. The names "The Jakarta Project", "Ant", and "Apache Software + * 4. The names "The Jakarta Project", "Commons", and "Apache Software * Foundation" must not be used to endorse or promote products derived * from this software without prior written permission. For written * permission, please contact apache@apache.org. * * 5. Products derived from this software may not be called "Apache" * nor may "Apache" appear in their names without prior written - * permission of the Apache Group. + * permission of the Apache Software Foundation. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES @@ -53,22 +53,32 @@ */ package org.apache.commons.lang.time; +import java.text.DateFormat; +import java.text.SimpleDateFormat; import java.util.Calendar; -import java.util.TimeZone; +import java.util.Date; +import java.util.Iterator; +import junit.framework.AssertionFailedError; import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; import junit.textui.TestRunner; /** - * TestCase for DateUtils. [Relies heavily on code taken from the - * DateUtilsTest class of the jakarata-ant project.] + * Unit tests {@link org.apache.commons.lang.CalendarUtils}. * - * @author Stephane Bailliez - * @author Stefan Bodewig + * @author Serge Knystautas */ public class DateUtilsTest extends TestCase { + DateFormat dateParser = null; + DateFormat dateTimeParser = null; + Date date1 = null; + Date date2 = null; + + public DateUtilsTest(String name) { + super(name); + } public static void main(String[] args) { TestRunner.run(suite()); @@ -76,69 +86,300 @@ public class DateUtilsTest extends TestCase { public static Test suite() { TestSuite suite = new TestSuite(DateUtilsTest.class); - suite.setName("DateUtils Tests"); + suite.setName("CalendarUtilsTest Tests"); return suite; } - public DateUtilsTest(String s) { - super(s); + protected void setUp() throws Exception { + super.setUp(); + + dateParser = new SimpleDateFormat("MMM dd, yyyy"); + dateTimeParser = new SimpleDateFormat("MMM dd, yyyy H:mm:ss.SSS"); + + date1 = dateTimeParser.parse("February 12, 2002 12:34:56.789"); + date2 = dateTimeParser.parse("November 18, 2001 1:23:11.321"); } - public void testElapsedTime(){ - String text = DateUtils.formatElapsedTime(50*1000); - assertEquals("50 seconds", text); - text = DateUtils.formatElapsedTime(65*1000); - assertEquals("1 minute 5 seconds", text); - text = DateUtils.formatElapsedTime(120*1000); - assertEquals("2 minutes 0 seconds", text); - text = DateUtils.formatElapsedTime(121*1000); - assertEquals("2 minutes 1 second", text); + protected void tearDown() throws Exception { + super.tearDown(); } - public void testDateTimeISO(){ - TimeZone timeZone = TimeZone.getTimeZone("GMT+1"); - Calendar cal = Calendar.getInstance(timeZone); - cal.set(2002,1,23,10,11,12); - String text = DateUtils.format(cal.getTime(), - DateUtils.ISO8601_DATETIME_PATTERN); - assertEquals("2002-02-23T09:11:12", text); + //----------------------------------------------------------------------- + + + /** + * Tests various values with the round method + */ + public void testRound() throws Exception { + assertEquals("round year-1 failed", + dateParser.parse("January 1, 2002"), + DateUtils.round(date1, Calendar.YEAR)); + assertEquals("round year-2 failed", + dateParser.parse("January 1, 2002"), + DateUtils.round(date2, Calendar.YEAR)); + assertEquals("round month-1 failed", + dateParser.parse("February 1, 2002"), + DateUtils.round(date1, Calendar.MONTH)); + assertEquals("round month-2 failed", + dateParser.parse("December 1, 2001"), + DateUtils.round(date2, Calendar.MONTH)); + assertEquals("round semimonth-1 failed", + dateParser.parse("February 16, 2002"), + DateUtils.round(date1, DateUtils.SEMI_MONTH)); + assertEquals("round semimonth-2 failed", + dateParser.parse("November 16, 2001"), + DateUtils.round(date2, DateUtils.SEMI_MONTH)); + assertEquals("round date-1 failed", + dateParser.parse("February 13, 2002"), + DateUtils.round(date1, Calendar.DATE)); + assertEquals("round date-2 failed", + dateParser.parse("November 18, 2001"), + DateUtils.round(date2, Calendar.DATE)); + assertEquals("round hour-1 failed", + dateTimeParser.parse("February 12, 2002 13:00:00.000"), + DateUtils.round(date1, Calendar.HOUR)); + assertEquals("round hour-2 failed", + dateTimeParser.parse("November 18, 2001 1:00:00.000"), + DateUtils.round(date2, Calendar.HOUR)); + assertEquals("round minute-1 failed", + dateTimeParser.parse("February 12, 2002 12:35:00.000"), + DateUtils.round(date1, Calendar.MINUTE)); + assertEquals("round minute-2 failed", + dateTimeParser.parse("November 18, 2001 1:23:00.000"), + DateUtils.round(date2, Calendar.MINUTE)); + assertEquals("round second-1 failed", + dateTimeParser.parse("February 12, 2002 12:34:57.000"), + DateUtils.round(date1, Calendar.SECOND)); + assertEquals("round second-2 failed", + dateTimeParser.parse("November 18, 2001 1:23:11.000"), + DateUtils.round(date2, Calendar.SECOND)); } - public void testDateISO(){ - TimeZone timeZone = TimeZone.getTimeZone("GMT"); - Calendar cal = Calendar.getInstance(timeZone); - cal.set(2002,1,23); - String text = DateUtils.format(cal.getTime(), - DateUtils.ISO8601_DATE_PATTERN); - assertEquals("2002-02-23", text); + /** + * Tests various values with the trunc method + */ + public void testTrunc() throws Exception { + assertEquals("trunc year-1 failed", + dateParser.parse("January 1, 2002"), + DateUtils.trunc(date1, Calendar.YEAR)); + assertEquals("trunc year-2 failed", + dateParser.parse("January 1, 2001"), + DateUtils.trunc(date2, Calendar.YEAR)); + assertEquals("trunc month-1 failed", + dateParser.parse("February 1, 2002"), + DateUtils.trunc(date1, Calendar.MONTH)); + assertEquals("trunc month-2 failed", + dateParser.parse("November 1, 2001"), + DateUtils.trunc(date2, Calendar.MONTH)); + assertEquals("trunc semimonth-1 failed", + dateParser.parse("February 1, 2002"), + DateUtils.trunc(date1, DateUtils.SEMI_MONTH)); + assertEquals("trunc semimonth-2 failed", + dateParser.parse("November 16, 2001"), + DateUtils.trunc(date2, DateUtils.SEMI_MONTH)); + assertEquals("trunc date-1 failed", + dateParser.parse("February 12, 2002"), + DateUtils.trunc(date1, Calendar.DATE)); + assertEquals("trunc date-2 failed", + dateParser.parse("November 18, 2001"), + DateUtils.trunc(date2, Calendar.DATE)); + assertEquals("trunc hour-1 failed", + dateTimeParser.parse("February 12, 2002 12:00:00.000"), + DateUtils.trunc(date1, Calendar.HOUR)); + assertEquals("trunc hour-2 failed", + dateTimeParser.parse("November 18, 2001 1:00:00.000"), + DateUtils.trunc(date2, Calendar.HOUR)); + assertEquals("trunc minute-1 failed", + dateTimeParser.parse("February 12, 2002 12:34:00.000"), + DateUtils.trunc(date1, Calendar.MINUTE)); + assertEquals("trunc minute-2 failed", + dateTimeParser.parse("November 18, 2001 1:23:00.000"), + DateUtils.trunc(date2, Calendar.MINUTE)); + assertEquals("trunc second-1 failed", + dateTimeParser.parse("February 12, 2002 12:34:56.000"), + DateUtils.trunc(date1, Calendar.SECOND)); + assertEquals("trunc second-2 failed", + dateTimeParser.parse("November 18, 2001 1:23:11.000"), + DateUtils.trunc(date2, Calendar.SECOND)); + } - public void testTimeISODate(){ - // make sure that elapsed time in set via date works - TimeZone timeZone = TimeZone.getTimeZone("GMT+1"); - Calendar cal = Calendar.getInstance(timeZone); - cal.set(2002,1,23, 21, 11, 12); - String text = DateUtils.format(cal.getTime(), - DateUtils.ISO8601_TIME_PATTERN); - assertEquals("20:11:12", text); + /** + * Tests the parse method, which is supposed to handle various strings + * as flexibly as CVS supports. + */ + public void testParse() throws Exception { + //This is difficult to test since the "now" used in the + // parse function cannot be controlled. We could possibly control + // it by trying before and after and making sure the value we expect + // is between the two values calculated. + //For now we're just using the custom assertEquals that takes a delta + + Calendar now = null; + + now = Calendar.getInstance(); + now.add(Calendar.MINUTE, -1); + assertEquals("parse 1 minute ago", + now, DateUtils.parse("1 minute ago"), 50); + now = Calendar.getInstance(); + now.add(Calendar.MINUTE, -8); + assertEquals("parse 8 minutes ago", + now, DateUtils.parse("8 minutes ago"), 50); + + now = Calendar.getInstance(); + now.add(Calendar.DATE, -1); + assertEquals("parse yesterday", + now, DateUtils.parse("yesterday"), 50); + + now = Calendar.getInstance(); + now.add(Calendar.DATE, 1); + assertEquals("parse tomorrow", + now, DateUtils.parse("tomorrow"), 50); + + now = Calendar.getInstance(); + //Sunday would be 1, Saturday would be 7, so we walk back up to 6 days. + if (now.get(Calendar.DAY_OF_WEEK) == 1) { + //If Sunday already, we go back a full week + now.add(Calendar.DATE, -7); + } else { + now.add(Calendar.DATE, 1 - now.get(Calendar.DAY_OF_WEEK)); + } + assertEquals("parse last Sunday", + now, DateUtils.parse("last Sunday"), 50); + + now = Calendar.getInstance(); + now.add(Calendar.DATE, -7); + assertEquals("parse last week", + now, DateUtils.parse("last week"), 50); + + now = Calendar.getInstance(); + //January would be 0, December would be 11, so we walk back up to 11 months + if (now.get(Calendar.MONTH) == 0) { + //If January already, we go back a full year + now.add(Calendar.MONTH, -12); + } else { + now.add(Calendar.MONTH, 0 - now.get(Calendar.MONTH)); + } + assertEquals("parse last January", + now, DateUtils.parse("last January"), 50); } - public void testTimeISO(){ - // make sure that elapsed time in ms works - long ms = (20*3600 + 11*60 + 12)*1000; - String text = DateUtils.format(ms, - DateUtils.ISO8601_TIME_PATTERN); - assertEquals("20:11:12", text); + /** + * Tests the calendar iterator for week ranges + */ + public void testWeekIterator() throws Exception { + Calendar now = Calendar.getInstance(); + for (int i = 0; i< 7; i++) { + Calendar today = DateUtils.trunc(now, Calendar.DATE); + Calendar sunday = DateUtils.trunc(now, Calendar.DATE); + sunday.add(Calendar.DATE, 1 - sunday.get(Calendar.DAY_OF_WEEK)); + Calendar monday = DateUtils.trunc(now, Calendar.DATE); + if (monday.get(Calendar.DAY_OF_WEEK) == 1) { + //This is sunday... roll back 6 days + monday.add(Calendar.DATE, -6); + } else { + monday.add(Calendar.DATE, 2 - monday.get(Calendar.DAY_OF_WEEK)); + } + Calendar centered = DateUtils.trunc(now, Calendar.DATE); + centered.add(Calendar.DATE, -3); + + Iterator it = DateUtils.getCalendarIterator(now, DateUtils.RANGE_WEEK_SUNDAY); + assertWeekIterator(it, sunday); + it = DateUtils.getCalendarIterator(now, DateUtils.RANGE_WEEK_MONDAY); + assertWeekIterator(it, monday); + it = DateUtils.getCalendarIterator(now, DateUtils.RANGE_WEEK_RELATIVE); + assertWeekIterator(it, today); + it = DateUtils.getCalendarIterator(now, DateUtils.RANGE_WEEK_CENTER); + assertWeekIterator(it, centered); + now.add(Calendar.DATE,1); + } } - public void testPhaseOfMoon() { - TimeZone timeZone = TimeZone.getTimeZone("GMT"); - Calendar cal = Calendar.getInstance(timeZone); - // should be full moon - cal.set(2002, 2, 27); - assertEquals(4, DateUtils.getPhaseOfMoon(cal)); - // should be new moon - cal.set(2002, 2, 12); - assertEquals(0, DateUtils.getPhaseOfMoon(cal)); + /** + * Tests the calendar iterator for month-based ranges + */ + public void testMonthIterator() throws Exception { + Iterator it = DateUtils.getCalendarIterator(date1, DateUtils.RANGE_MONTH_SUNDAY); + assertWeekIterator(it, + dateParser.parse("January 27, 2002"), + dateParser.parse("March 2, 2002")); + + it = DateUtils.getCalendarIterator(date1, DateUtils.RANGE_MONTH_MONDAY); + assertWeekIterator(it, + dateParser.parse("January 28, 2002"), + dateParser.parse("March 3, 2002")); + + it = DateUtils.getCalendarIterator(date2, DateUtils.RANGE_MONTH_SUNDAY); + assertWeekIterator(it, + dateParser.parse("October 28, 2001"), + dateParser.parse("December 1, 2001")); + + it = DateUtils.getCalendarIterator(date2, DateUtils.RANGE_MONTH_MONDAY); + assertWeekIterator(it, + dateParser.parse("October 29, 2001"), + dateParser.parse("December 2, 2001")); + } + + /** + * This checks that this is a 7 element iterator of Calendar objects + * that are dates (no time), and exactly 1 day spaced after each other. + */ + private static void assertWeekIterator(Iterator it, Calendar start) { + Calendar end = (Calendar) start.clone(); + end.add(Calendar.DATE, 6); + + assertWeekIterator(it, start, end); + } + + /** + * Convenience method for when working with Date objects + */ + private static void assertWeekIterator(Iterator it, Date start, Date end) { + Calendar calStart = Calendar.getInstance(); + calStart.setTime(start); + Calendar calEnd = Calendar.getInstance(); + calEnd.setTime(end); + + assertWeekIterator(it, calStart, calEnd); + } + + /** + * This checks that this is a 7 divisble iterator of Calendar objects + * that are dates (no time), and exactly 1 day spaced after each other + * (in addition to the proper start and stop dates) + */ + private static void assertWeekIterator(Iterator it, Calendar start, Calendar end) { + Calendar cal = (Calendar) it.next(); + assertEquals("", start, cal, 0); + Calendar last = null; + int count = 1; + while (it.hasNext()) { + //Check this is just a date (no time component) + assertEquals("", cal, DateUtils.trunc(cal, Calendar.DATE), 0); + + last = cal; + cal = (Calendar) it.next(); + count++; + + //Check that this is one day more than the last date + last.add(Calendar.DATE, 1); + assertEquals("", last, cal, 0); + } + if (count % 7 != 0) { + throw new AssertionFailedError("There were " + count + " days in this iterator"); + } + assertEquals("", end, cal, 0); + } + + /** + * Used to check that Calendar objects are close enough + * delta is in milliseconds + */ + public static void assertEquals(String message, Calendar cal1, Calendar cal2, long delta) { + if (Math.abs(cal1.getTime().getTime() - cal2.getTime().getTime()) > delta) { + throw new AssertionFailedError( + message + " expected " + cal1.getTime() + " but got " + cal2.getTime()); + } } } + diff --git a/src/test/org/apache/commons/lang/time/DurationFormatUtilsTest.java b/src/test/org/apache/commons/lang/time/DurationFormatUtilsTest.java new file mode 100644 index 000000000..75365b80c --- /dev/null +++ b/src/test/org/apache/commons/lang/time/DurationFormatUtilsTest.java @@ -0,0 +1,162 @@ +/* ==================================================================== + * The Apache Software License, Version 1.1 + * + * Copyright (c) 2002-2003 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowlegement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowlegement may appear in the software itself, + * if and wherever such third-party acknowlegements normally appear. + * + * 4. The names "The Jakarta Project", "Commons", and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Software Foundation. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + */ +package org.apache.commons.lang.time; + +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestSuite; +import junit.textui.TestRunner; + +/** + * TestCase for DurationFormatUtils. + * + * @author Apache Ant - DateUtilsTest + * @author Stephane Bailliez + * @author Stefan Bodewig + * @author Stephen Colebourne + */ +public class DurationFormatUtilsTest extends TestCase { + + public static void main(String[] args) { + TestRunner.run(suite()); + } + + public static Test suite() { + TestSuite suite = new TestSuite(DurationFormatUtilsTest.class); + suite.setName("DurationFormatUtils Tests"); + return suite; + } + + public DurationFormatUtilsTest(String s) { + super(s); + } + + public void testFormatWords(){ + String text = null; + + text = DurationFormatUtils.formatWords(50*1000, true, false); + assertEquals("50 seconds", text); + text = DurationFormatUtils.formatWords(65*1000, true, false); + assertEquals("1 minute 5 seconds", text); + text = DurationFormatUtils.formatWords(120*1000, true, false); + assertEquals("2 minutes 0 seconds", text); + text = DurationFormatUtils.formatWords(121*1000, true, false); + assertEquals("2 minutes 1 second", text); + text = DurationFormatUtils.formatWords(72*60*1000, true, false); + assertEquals("1 hour 12 minutes 0 seconds", text); + + text = DurationFormatUtils.formatWords(50*1000, true, true); + assertEquals("50 seconds", text); + text = DurationFormatUtils.formatWords(65*1000, true, true); + assertEquals("1 minute 5 seconds", text); + text = DurationFormatUtils.formatWords(120*1000, true, true); + assertEquals("2 minutes", text); + text = DurationFormatUtils.formatWords(121*1000, true, true); + assertEquals("2 minutes 1 second", text); + text = DurationFormatUtils.formatWords(72*60*1000, true, true); + assertEquals("1 hour 12 minutes", text); + + text = DurationFormatUtils.formatWords(50*1000, false, true); + assertEquals("0 days 0 hours 0 minutes 50 seconds", text); + text = DurationFormatUtils.formatWords(65*1000, false, true); + assertEquals("0 days 0 hours 1 minute 5 seconds", text); + text = DurationFormatUtils.formatWords(120*1000, false, true); + assertEquals("0 days 0 hours 2 minutes", text); + text = DurationFormatUtils.formatWords(121*1000, false, true); + assertEquals("0 days 0 hours 2 minutes 1 second", text); + text = DurationFormatUtils.formatWords(72*60*1000, false, true); + assertEquals("0 days 1 hour 12 minutes", text); + + text = DurationFormatUtils.formatWords(50*1000, false, false); + assertEquals("0 days 0 hours 0 minutes 50 seconds", text); + text = DurationFormatUtils.formatWords(65*1000, false, false); + assertEquals("0 days 0 hours 1 minute 5 seconds", text); + text = DurationFormatUtils.formatWords(120*1000, false, false); + assertEquals("0 days 0 hours 2 minutes 0 seconds", text); + text = DurationFormatUtils.formatWords(121*1000, false, false); + assertEquals("0 days 0 hours 2 minutes 1 second", text); + text = DurationFormatUtils.formatWords(72*60*1000, false, false); + assertEquals("0 days 1 hour 12 minutes 0 seconds", text); + } + + public void testFormatISOStyle(){ + long time = 0; + assertEquals("0:00:00.000", DurationFormatUtils.formatISO(time)); + + time = 1; + assertEquals("0:00:00.001", DurationFormatUtils.formatISO(time)); + + time = 15; + assertEquals("0:00:00.015", DurationFormatUtils.formatISO(time)); + + time = 165; + assertEquals("0:00:00.165", DurationFormatUtils.formatISO(time)); + + time = 1675; + assertEquals("0:00:01.675", DurationFormatUtils.formatISO(time)); + + time = 13465; + assertEquals("0:00:13.465", DurationFormatUtils.formatISO(time)); + + time = 72789; + assertEquals("0:01:12.789", DurationFormatUtils.formatISO(time)); + + time = 12789 + 32 * 60000; + assertEquals("0:32:12.789", DurationFormatUtils.formatISO(time)); + + time = 12789 + 62 * 60000; + assertEquals("1:02:12.789", DurationFormatUtils.formatISO(time)); + } + +} diff --git a/src/test/org/apache/commons/lang/time/FastDateFormatTest.java b/src/test/org/apache/commons/lang/time/FastDateFormatTest.java index 749efe43f..2b6511322 100644 --- a/src/test/org/apache/commons/lang/time/FastDateFormatTest.java +++ b/src/test/org/apache/commons/lang/time/FastDateFormatTest.java @@ -54,6 +54,8 @@ package org.apache.commons.lang.time; import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.GregorianCalendar; import java.util.Locale; import java.util.TimeZone; @@ -67,7 +69,7 @@ import junit.textui.TestRunner; * * @author Sean Schofield * @since 2.0 - * @version $Id: FastDateFormatTest.java,v 1.3 2003/05/21 23:41:21 scolebourne Exp $ + * @version $Id: FastDateFormatTest.java,v 1.4 2003/06/08 23:14:23 scolebourne Exp $ */ public class FastDateFormatTest extends TestCase { @@ -106,9 +108,15 @@ public class FastDateFormatTest extends TestCase { public void test_getInstance_String() { FastDateFormat format1 = FastDateFormat.getInstance("MM/DD/yyyy"); FastDateFormat format2 = FastDateFormat.getInstance("MM-DD-yyyy"); + FastDateFormat format3 = FastDateFormat.getInstance("MM-DD-yyyy"); + assertTrue(format1 != format2); // -- junit 3.8 version -- assertFalse(format1 == format2); - assertSame(format1, FastDateFormat.getInstance("MM/DD/yyyy")); + assertSame(format2, format3); assertEquals("MM/DD/yyyy", format1.getPattern()); + assertEquals(TimeZone.getDefault(), format1.getTimeZone()); + assertEquals(TimeZone.getDefault(), format2.getTimeZone()); + assertEquals(false, format1.getTimeZoneOverridesCalendar()); + assertEquals(false, format2.getTimeZoneOverridesCalendar()); } public void test_getInstance_String_TimeZone() { @@ -124,12 +132,16 @@ public class FastDateFormatTest extends TestCase { FastDateFormat format3 = FastDateFormat.getInstance("MM/DD/yyyy", TimeZone.getDefault()); FastDateFormat format4 = FastDateFormat.getInstance("MM/DD/yyyy", TimeZone.getDefault()); FastDateFormat format5 = FastDateFormat.getInstance("MM-DD-yyyy", TimeZone.getDefault()); + FastDateFormat format6 = FastDateFormat.getInstance("MM-DD-yyyy"); assertTrue(format1 != format2); // -- junit 3.8 version -- assertFalse(format1 == format2); - assertTrue(format1.getTimeZone().equals(TimeZone.getTimeZone("Atlantic/Reykjavik"))); - assertNull(format2.getTimeZone()); + assertEquals(TimeZone.getTimeZone("Atlantic/Reykjavik"), format1.getTimeZone()); + assertEquals(true, format1.getTimeZoneOverridesCalendar()); + assertEquals(TimeZone.getDefault(), format2.getTimeZone()); + assertEquals(false, format2.getTimeZoneOverridesCalendar()); assertSame(format3, format4); assertTrue(format3 != format5); // -- junit 3.8 version -- assertFalse(format3 == format5); + assertTrue(format4 != format6); // -- junit 3.8 version -- assertFalse(format3 == format5); } finally { Locale.setDefault(realDefaultLocale); @@ -168,10 +180,15 @@ public class FastDateFormatTest extends TestCase { TimeZone.getDefault(), Locale.GERMANY); assertTrue(format1 != format2); // -- junit 3.8 version -- assertNotSame(format1, format2); - assertEquals(format1.getTimeZone(), TimeZone.getTimeZone("Atlantic/Reykjavik")); - assertNull(format2.getTimeZone()); - assertEquals(format3.getTimeZone(), TimeZone.getDefault()); - assertEquals(format3.getTimeZone(), TimeZone.getTimeZone("America/New_York")); + assertEquals(TimeZone.getTimeZone("Atlantic/Reykjavik"), format1.getTimeZone()); + assertEquals(TimeZone.getDefault(), format2.getTimeZone()); + assertEquals(TimeZone.getDefault(), format3.getTimeZone()); + assertEquals(true, format1.getTimeZoneOverridesCalendar()); + assertEquals(false, format2.getTimeZoneOverridesCalendar()); + assertEquals(true, format3.getTimeZoneOverridesCalendar()); + assertEquals(Locale.GERMANY, format1.getLocale()); + assertEquals(Locale.GERMANY, format2.getLocale()); + assertEquals(Locale.GERMANY, format3.getLocale()); } finally { Locale.setDefault(realDefaultLocale); @@ -179,4 +196,55 @@ public class FastDateFormatTest extends TestCase { } } + public void testFormat() { + Locale realDefaultLocale = Locale.getDefault(); + TimeZone realDefaultZone = TimeZone.getDefault(); + try { + Locale.setDefault(Locale.US); + TimeZone.setDefault(TimeZone.getTimeZone("America/New_York")); + FastDateFormat fdf = null; + SimpleDateFormat sdf = null; + + GregorianCalendar cal1 = new GregorianCalendar(2003, 0, 10, 15, 33, 20); + GregorianCalendar cal2 = new GregorianCalendar(2003, 6, 10, 9, 00, 00); + Date date1 = cal1.getTime(); + Date date2 = cal2.getTime(); + + fdf = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss"); + sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); + assertEquals(sdf.format(date1), fdf.format(date1)); + assertEquals("2003-01-10T15:33:20", fdf.format(date1)); + assertEquals("2003-01-10T15:33:20", fdf.format(cal1)); + assertEquals("2003-07-10T09:00:00", fdf.format(date2)); + assertEquals("2003-07-10T09:00:00", fdf.format(cal2)); + + fdf = FastDateFormat.getInstance("Z"); + assertEquals("-0500", fdf.format(date1)); + assertEquals("-0500", fdf.format(cal1)); + + fdf = FastDateFormat.getInstance("Z"); + assertEquals("-0400", fdf.format(date2)); + assertEquals("-0400", fdf.format(cal2)); + + fdf = FastDateFormat.getInstance("ZZ"); + assertEquals("-05:00", fdf.format(date1)); + assertEquals("-05:00", fdf.format(cal1)); + + fdf = FastDateFormat.getInstance("ZZ"); + assertEquals("-04:00", fdf.format(date2)); + assertEquals("-04:00", fdf.format(cal2)); + + String pattern = "GGGG GGG GG G yyyy yyy yy y MMMM MMM MM M" + + " dddd ddd dd d DDDD DDD DD D EEEE EEE EE E aaaa aaa aa a zzzz zzz zz z"; + fdf = FastDateFormat.getInstance(pattern); + sdf = new SimpleDateFormat(pattern); + assertEquals(sdf.format(date1), fdf.format(date1)); + assertEquals(sdf.format(date2), fdf.format(date2)); + + } finally { + Locale.setDefault(realDefaultLocale); + TimeZone.setDefault(realDefaultZone); + } + } + } diff --git a/src/test/org/apache/commons/lang/time/StopWatchTest.java b/src/test/org/apache/commons/lang/time/StopWatchTest.java index 689825825..eeb055fc5 100644 --- a/src/test/org/apache/commons/lang/time/StopWatchTest.java +++ b/src/test/org/apache/commons/lang/time/StopWatchTest.java @@ -62,7 +62,7 @@ import junit.textui.TestRunner; * TestCase for StopWatch. * * @author Stephen Colebourne - * @version $Id: StopWatchTest.java,v 1.2 2003/05/21 23:40:24 scolebourne Exp $ + * @version $Id: StopWatchTest.java,v 1.3 2003/06/08 23:14:23 scolebourne Exp $ */ public class StopWatchTest extends TestCase { @@ -80,35 +80,6 @@ public class StopWatchTest extends TestCase { super(s); } - public void testToString(){ - long time = 0; - assertEquals("0:00:00.000", StopWatch.toString(time)); - - time = 1; - assertEquals("0:00:00.001", StopWatch.toString(time)); - - time = 15; - assertEquals("0:00:00.015", StopWatch.toString(time)); - - time = 165; - assertEquals("0:00:00.165", StopWatch.toString(time)); - - time = 1675; - assertEquals("0:00:01.675", StopWatch.toString(time)); - - time = 13465; - assertEquals("0:00:13.465", StopWatch.toString(time)); - - time = 72789; - assertEquals("0:01:12.789", StopWatch.toString(time)); - - time = 12789 + 32 * 60000; - assertEquals("0:32:12.789", StopWatch.toString(time)); - - time = 12789 + 62 * 60000; - assertEquals("1:02:12.789", StopWatch.toString(time)); - } - public void testStopWatchSimple(){ StopWatch watch = new StopWatch(); assertEquals(0, watch.getTime()); diff --git a/src/test/org/apache/commons/lang/time/TimeTestSuite.java b/src/test/org/apache/commons/lang/time/TimeTestSuite.java index c7e62424d..65c232b93 100644 --- a/src/test/org/apache/commons/lang/time/TimeTestSuite.java +++ b/src/test/org/apache/commons/lang/time/TimeTestSuite.java @@ -1,7 +1,7 @@ /* ==================================================================== * The Apache Software License, Version 1.1 * - * Copyright (c) 2002 The Apache Software Foundation. All rights + * Copyright (c) 2002-2003 The Apache Software Foundation. All rights * reserved. * * Redistribution and use in source and binary forms, with or without @@ -57,11 +57,12 @@ import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; import junit.textui.TestRunner; + /** * Test suite for the Time package. * * @author Stephen Colebourne - * @version $Id: TimeTestSuite.java,v 1.3 2003/01/10 03:55:01 bayard Exp $ + * @version $Id: TimeTestSuite.java,v 1.4 2003/06/08 23:14:23 scolebourne Exp $ */ public class TimeTestSuite extends TestCase { @@ -85,8 +86,9 @@ public class TimeTestSuite extends TestCase { public static Test suite() { TestSuite suite = new TestSuite(); suite.setName("Commons-Lang-Time Tests"); - suite.addTest(CalendarUtilsTest.suite()); suite.addTest(DateUtilsTest.suite()); + suite.addTest(DateFormatUtilsTest.suite()); + suite.addTest(DurationFormatUtilsTest.suite()); suite.addTest(StopWatchTest.suite()); suite.addTest(FastDateFormatTest.suite()); return suite;