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
This commit is contained in:
Stephen Colebourne 2003-06-08 23:14:23 +00:00
parent 9bb3f9b9a3
commit 73ee6c3d27
13 changed files with 1912 additions and 1381 deletions

View File

@ -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
* <http://www.apache.org/>.
*/
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 <a href="mailto:sergek@lokitech.com">Serge Knystautas</a>
* @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);
}
}
}

View File

@ -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
* <http://www.apache.org/>.
*/
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.
* <p>
* Formatting is performed using the
* {@link org.apache.commons.lang.time.FastDateFormat} class.
*
* @author Apache Ant - DateUtils
* @author <a href="mailto:sbailliez@apache.org">Stephane Bailliez</a>
* @author <a href="mailto:stefan.bodewig@epost.de">Stefan Bodewig</a>
* @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 <tt>yyyy-MM-dd'T'HH:mm:ss</tt>.
*/
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 <tt>yyyy-MM-dd'T'HH:mm:ssZZ</tt>.
*/
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 <tt>yyyy-MM-dd</tt>.
*/
public static final FastDateFormat ISO_DATE_FORMAT
= FastDateFormat.getInstance("yyyy-MM-dd");
/**
* ISO8601-like formatter for date with timezone.
* The format used is <tt>yyyy-MM-ddZZ</tt>.
* 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 <tt>'T'HH:mm:ss</tt>.
*/
public static final FastDateFormat ISO_TIME_FORMAT
= FastDateFormat.getInstance("'T'HH:mm:ss");
/**
* ISO8601 formatter for time with timezone.
* The format used is <tt>'T'HH:mm:ssZZ</tt>.
*/
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 <tt>HH:mm:ss</tt>.
* 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 <tt>HH:mm:ssZZ</tt>.
* 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 <tt>EEE, dd MMM yyyy HH:mm:ss Z</tt> 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.
* <p>
* 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);
}
}

View File

@ -53,209 +53,491 @@
*/ */
package org.apache.commons.lang.time; package org.apache.commons.lang.time;
import java.text.ChoiceFormat;
import java.text.DateFormat; import java.text.DateFormat;
import java.text.MessageFormat; import java.text.DateFormatSymbols;
import java.text.ParseException;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.Calendar; import java.util.Calendar;
import java.util.Date; import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Iterator;
import java.util.Locale; import java.util.Locale;
import java.util.NoSuchElementException;
import java.util.TimeZone; import java.util.TimeZone;
/** /**
* Helper methods to deal with date/time formatting. [Relies heavily on * A suite of utilities surrounding the use of the Calendar and Date object.
* code taken from the DateUtils class of the jakarata-ant project.]
* *
* @author <a href="mailto:sbailliez@apache.org">Stephane Bailliez</a> * @author <a href="mailto:sergek@lokitech.com">Serge Knystautas</a>
* @author <a href="mailto:stefan.bodewig@epost.de">Stefan Bodewig</a> * @author Stephen Colebourne
* @since 2.1 * @since 2.0
* @version $Id: DateUtils.java,v 1.2 2003/02/04 22:19:33 scolebourne Exp $ * @version $Id: DateUtils.java,v 1.3 2003/06/08 23:14:23 scolebourne Exp $
*/ */
public final class DateUtils { public class DateUtils {
/** /**
* ISO8601-like pattern for date-time. It does not support timezone. * The UTC timezone (often referred to as GMT).
* <tt>yyyy-MM-ddTHH:mm:ss</tt>
*/ */
public static final String ISO8601_DATETIME_PATTERN public static final TimeZone UTC_TIMEZONE = TimeZone.getTimeZone("GMT");
= "yyyy-MM-dd'T'HH:mm:ss"; /**
* 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. <tt>yyyy-MM-dd</tt> * 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_DATE_PATTERN public final static int SEMI_MONTH = 1001;
= "yyyy-MM-dd";
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 time. <tt>HH:mm:ss</tt> * A week range, starting on Sunday.
*/ */
public static final String ISO8601_TIME_PATTERN public final static int RANGE_WEEK_SUNDAY = 1;
= "HH:mm:ss";
/** /**
* Format used for SMTP (and probably other) Date headers. * A week range, starting on Monday.
*/ */
public static final DateFormat DATE_HEADER_FORMAT public final static int RANGE_WEEK_MONDAY = 2;
= new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss ", Locale.US);
/**
* A week range, starting on the day focused.
*/
public final static int RANGE_WEEK_RELATIVE = 3;
// code from Magesh moved from DefaultLogger and slightly modified /**
private static final MessageFormat MINUTE_SECONDS * A week range, centered around the day focused.
= new MessageFormat("{0}{1}"); */
public final static int RANGE_WEEK_CENTER = 4;
private static final double[] LIMITS = {0, 1, 2}; /**
* A month range, the week starting on Sunday.
*/
public final static int RANGE_MONTH_SUNDAY = 5;
private static final String[] MINUTES_PART = /**
{"", "1 minute ", "{0,number} minutes "}; * A month range, the week starting on Monday.
*/
public final static int RANGE_MONTH_MONDAY = 6;
private static final String[] SECONDS_PART = /**
{"0 seconds", "1 second", "{1,number} seconds"}; * See the other round method. Works with a Date object.
*/
private static final ChoiceFormat MINUTES_FORMAT = public static Date round(Date val, int field) {
new ChoiceFormat(LIMITS, MINUTES_PART); GregorianCalendar gval = new GregorianCalendar();
gval.setTime(val);
private static final ChoiceFormat SECONDS_FORMAT = modify(gval, field, true);
new ChoiceFormat(LIMITS, SECONDS_PART); return gval.getTime();
static {
MINUTE_SECONDS.setFormat(0, MINUTES_FORMAT);
MINUTE_SECONDS.setFormat(1, SECONDS_FORMAT);
} }
/** /**
* <p>DateUtils instances should NOT be constructed in standard programming.</p> * Round this date, leaving the field specified as the most significant
* * field. For example, if you had the datetime of 28 Mar 2002
* <p>This constructor is public to permit tools that require a JavaBean instance * 13:45:01.231, if this was passed with HOUR, it would return 28 Mar
* to operate.</p> * 2002 14:00:00.000. If this was passed with MONTH, it would return
* 1 April 2002 0:00:00.000.
*/ */
public DateUtils() { public static Calendar round(Calendar val, int field) {
} Calendar rounded = (Calendar) val.clone();
modify(rounded, field, true);
return rounded;
/**
* 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.
* <ul>
* <li>minutes are not displayed when 0. (ie: "45 seconds")</li>
* <li>seconds are always displayed in plural form (ie "0 seconds" or
* "10 seconds") except for 1 (ie "1 second")</li>
* </ul>
* @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);
} }
/** /**
* return a lenient date format set to GMT time zone. * See the other round method. Works with an Object, trying to
* @param pattern the pattern used for date/time formatting. * use it as either a Date or Calendar.
* @return the configured format for this pattern.
*/ */
private static DateFormat createDateFormat(String pattern) { public static Date round(Object val, int field) {
SimpleDateFormat sdf = new SimpleDateFormat(pattern); if (val instanceof Date) {
TimeZone gmt = TimeZone.getTimeZone("GMT"); return round((Date) val, field);
sdf.setTimeZone(gmt); } else if (val instanceof Calendar) {
sdf.setLenient(true); return round((Calendar) val, field).getTime();
return sdf; } else {
throw new ClassCastException("Could not round " + val);
}
} }
/** /**
* Calculate the phase of the moon for a given date. * See the other trunc method. Works with a Date.
*
* <p>Code heavily influenced by hacklib.c in <a
* href="http://www.nethack.org/">Nethack</a></p>
*
* <p>The Algorithm:
*
* <pre>
* 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
* </pre>
*
* @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) { public static Date trunc(Date val, int field) {
int dayOfTheYear = cal.get(Calendar.DAY_OF_YEAR); GregorianCalendar gval = new GregorianCalendar();
int yearInMetonicCycle = ((cal.get(Calendar.YEAR) - 1900) % 19) + 1; gval.setTime(val);
int epact = (11 * yearInMetonicCycle + 18) % 30; modify(gval, field, false);
if ((epact == 25 && yearInMetonicCycle > 11) || epact == 24) { return gval.getTime();
epact++;
}
return (((((dayOfTheYear + epact) * 6) + 11) % 177) / 22) & 7;
} }
/** /**
* Returns the current Date in a format suitable for a SMTP date * Truncate this date, leaving the field specified as the most significant
* header. * 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
* @since Ant 1.5.2 * 2002 13:00:00.000. If this was passed with MONTH, it would return
* 1 Mar 2002 0:00:00.000.
*/ */
public static String getDateForHeader() { 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(); Calendar cal = Calendar.getInstance();
TimeZone tz = cal.getTimeZone(); cal.setTime(datetime);
int offset = tz.getOffset(cal.get(Calendar.ERA), return cal;
cal.get(Calendar.YEAR), } catch (ParseException pe) {
cal.get(Calendar.MONTH), //we ignore this and just keep trying
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();
} }
} }
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);
}
}
}

View File

@ -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
* <http://www.apache.org/>.
*/
package org.apache.commons.lang.time;
/**
* Duration formatting utilites and constants.
*
* @author Apache Ant - DateUtils
* @author <a href="mailto:sbailliez@apache.org">Stephane Bailliez</a>
* @author <a href="mailto:stefan.bodewig@epost.de">Stefan Bodewig</a>
* @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.
* <p>
* 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.
* <ul>
* <li>minutes are not displayed when 0. (ie: "45 seconds")</li>
* <li>seconds are always displayed in plural form (ie "0 seconds" or
* "10 seconds") except for 1 (ie "1 second")</li>
* </ul>
*
* @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();
}
/**
* <p>Get the time gap as a string.</p>
*
* <p>The format used is ISO8601-like.
* <i>hours</i>:<i>minutes</i>:<i>seconds</i>.<i>milliseconds</i>.</p>
*
* @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();
}
}

View File

@ -53,7 +53,6 @@
*/ */
package org.apache.commons.lang.time; package org.apache.commons.lang.time;
import java.io.Serializable;
import java.text.DateFormat; import java.text.DateFormat;
import java.text.DateFormatSymbols; import java.text.DateFormatSymbols;
import java.text.FieldPosition; import java.text.FieldPosition;
@ -71,11 +70,18 @@ import java.util.Map;
import java.util.TimeZone; import java.util.TimeZone;
/** /**
* FastDateFormat is similar to {@link java.text.SimpleDateFormat}, but * FastDateFormat is a fast and thread-safe version of {@link java.text.SimpleDateFormat}.
* faster and thread-safe.
* <p> * <p>
* Only formatting is supported, but all patterns are compatible with * Only formatting is supported, but all patterns are compatible with
* SimpleDateFormat. * SimpleDateFormat (except timezones - see below).
* <p>
* 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).
* <p>
* 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.
* <p> * <p>
* NOTE: Code originally taken from the open source TreeTrove project. * NOTE: Code originally taken from the open source TreeTrove project.
* *
@ -84,24 +90,34 @@ import java.util.TimeZone;
* @author Gary Gregory * @author Gary Gregory
* @author Stephen Colebourne * @author Stephen Colebourne
* @since 2.0 * @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 { 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; 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; 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; 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; public static final int SHORT = SimpleDateFormat.SHORT;
// package scoped as used by inner class // package scoped as used by inner class
static final double LOG_10 = Math.log(10); static final double LOG_10 = Math.log(10);
private static String cDefaultPattern; private static String cDefaultPattern;
private static TimeZone cDefaultTimeZone = TimeZone.getDefault();
private static Map cInstanceCache = new HashMap(7); private static Map cInstanceCache = new HashMap(7);
private static Map cDateInstanceCache = new HashMap(7); private static Map cDateInstanceCache = new HashMap(7);
@ -113,12 +129,16 @@ public class FastDateFormat extends Format {
private final String mPattern; private final String mPattern;
/** The time zone */ /** The time zone */
private final TimeZone mTimeZone; private final TimeZone mTimeZone;
/** Whether the time zone overrides any on Calendars */
private final boolean mTimeZoneForced;
/** The locale */ /** The locale */
private final Locale mLocale; private final Locale mLocale;
/** Whether the locale overrides the default */
private final boolean mLocaleForced;
/** The parsed rules */ /** The parsed rules */
private final Rule[] mRules; private Rule[] mRules;
/** The estimated maximum length */ /** 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 timeZone optional time zone, overrides time zone of formatted date
* @param locale optional locale, overrides system locale * @param locale optional locale, overrides system locale
* @return a pattern based date/time formatter * @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) { public static synchronized FastDateFormat getInstance(String pattern, TimeZone timeZone, Locale locale) {
Object key = pattern; FastDateFormat emptyFormat = new FastDateFormat(pattern, timeZone, locale);
if (timeZone != null) { FastDateFormat format = (FastDateFormat) cInstanceCache.get(emptyFormat);
key = new Pair(key, timeZone);
}
if (locale != null) {
key = new Pair(key, locale);
}
FastDateFormat format = (FastDateFormat) cInstanceCache.get(key);
if (format == null) { if (format == null) {
if (locale == null) { format = emptyFormat;
locale = Locale.getDefault(); format.init(); // convert shell format into usable one
} cInstanceCache.put(format, format); // this is OK!
format = new FastDateFormat(pattern, timeZone, locale, new DateFormatSymbols(locale));
cInstanceCache.put(key, format);
} }
return format; return format;
} }
@ -342,17 +352,62 @@ public class FastDateFormat extends Format {
return cDefaultPattern; 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. * 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 * @return a List of Rule objects
* @throws IllegalArgumentException if pattern is invalid * @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(); List rules = new ArrayList();
String[] ERAs = symbols.getEras(); String[] ERAs = symbols.getEras();
@ -362,12 +417,12 @@ public class FastDateFormat extends Format {
String[] shortWeekdays = symbols.getShortWeekdays(); String[] shortWeekdays = symbols.getShortWeekdays();
String[] AmPmStrings = symbols.getAmPmStrings(); String[] AmPmStrings = symbols.getAmPmStrings();
int length = pattern.length(); int length = mPattern.length();
int[] indexRef = new int[1]; int[] indexRef = new int[1];
for (int i = 0; i < length; i++) { for (int i = 0; i < length; i++) {
indexRef[0] = i; indexRef[0] = i;
String token = parseToken(pattern, indexRef); String token = parseToken(mPattern, indexRef);
i = indexRef[0]; i = indexRef[0];
int tokenLen = token.length(); int tokenLen = token.length();
@ -384,9 +439,9 @@ public class FastDateFormat extends Format {
break; break;
case 'y': // year (number) case 'y': // year (number)
if (tokenLen >= 4) { if (tokenLen >= 4) {
rule = new UnpaddedNumberField(Calendar.YEAR); rule = UnpaddedNumberField.INSTANCE_YEAR;
} else { } else {
rule = new TwoDigitYearField(); rule = TwoDigitYearField.INSTANCE;
} }
break; break;
case 'M': // month in year (text and number) case 'M': // month in year (text and number)
@ -395,9 +450,9 @@ public class FastDateFormat extends Format {
} else if (tokenLen == 3) { } else if (tokenLen == 3) {
rule = new TextField(Calendar.MONTH, shortMonths); rule = new TextField(Calendar.MONTH, shortMonths);
} else if (tokenLen == 2) { } else if (tokenLen == 2) {
rule = new TwoDigitMonthField(); rule = TwoDigitMonthField.INSTANCE;
} else { } else {
rule = new UnpaddedMonthField(); rule = UnpaddedMonthField.INSTANCE;
} }
break; break;
case 'd': // day in month (number) case 'd': // day in month (number)
@ -444,9 +499,16 @@ public class FastDateFormat extends Format {
break; break;
case 'z': // time zone (text) case 'z': // time zone (text)
if (tokenLen >= 4) { if (tokenLen >= 4) {
rule = new TimeZoneRule(timeZone, locale, TimeZone.LONG); rule = new TimeZoneNameRule(mTimeZone, mTimeZoneForced, mLocale, TimeZone.LONG);
} else { } 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; break;
case '\'': // literal text case '\'': // literal text
@ -474,7 +536,7 @@ public class FastDateFormat extends Format {
* @param indexRef index references * @param indexRef index references
* @return parsed token * @return parsed token
*/ */
private static String parseToken(String pattern, int[] indexRef) { protected String parseToken(String pattern, int[] indexRef) {
StringBuffer buf = new StringBuffer(); StringBuffer buf = new StringBuffer();
int i = indexRef[0]; int i = indexRef[0];
@ -533,7 +595,7 @@ public class FastDateFormat extends Format {
* @param padding the padding required * @param padding the padding required
* @return a new rule with the correct padding * @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) { switch (padding) {
case 1: case 1:
return new UnpaddedNumberField(field); return new UnpaddedNumberField(field);
@ -544,40 +606,7 @@ public class FastDateFormat extends Format {
} }
} }
//----------------------------------------------------------------------- // Format methods
/**
* 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 either a Date or a Calendar object. * Format either a Date or a Calendar object.
@ -605,11 +634,8 @@ public class FastDateFormat extends Format {
* @return the formatted string * @return the formatted string
*/ */
public String format(Date date) { public String format(Date date) {
Calendar c = new GregorianCalendar(cDefaultTimeZone); Calendar c = new GregorianCalendar(mTimeZone);
c.setTime(date); c.setTime(date);
if (mTimeZone != null) {
c.setTimeZone(mTimeZone);
}
return applyRules(c, new StringBuffer(mMaxLengthEstimate)).toString(); return applyRules(c, new StringBuffer(mMaxLengthEstimate)).toString();
} }
@ -631,11 +657,8 @@ public class FastDateFormat extends Format {
* @return the specified string buffer * @return the specified string buffer
*/ */
public StringBuffer format(Date date, StringBuffer buf) { public StringBuffer format(Date date, StringBuffer buf) {
Calendar c = new GregorianCalendar(cDefaultTimeZone); Calendar c = new GregorianCalendar(mTimeZone);
c.setTime(date); c.setTime(date);
if (mTimeZone != null) {
c.setTimeZone(mTimeZone);
}
return applyRules(c, buf); return applyRules(c, buf);
} }
@ -647,7 +670,7 @@ public class FastDateFormat extends Format {
* @return the specified string buffer * @return the specified string buffer
*/ */
public StringBuffer format(Calendar calendar, StringBuffer buf) { public StringBuffer format(Calendar calendar, StringBuffer buf) {
if (mTimeZone != null) { if (mTimeZoneForced) {
calendar = (Calendar) calendar.clone(); calendar = (Calendar) calendar.clone();
calendar.setTimeZone(mTimeZone); calendar.setTimeZone(mTimeZone);
} }
@ -661,7 +684,7 @@ public class FastDateFormat extends Format {
* @param buf the buffer to format into * @param buf the buffer to format into
* @return the specified string buffer * @return the specified string buffer
*/ */
private StringBuffer applyRules(Calendar calendar, StringBuffer buf) { protected StringBuffer applyRules(Calendar calendar, StringBuffer buf) {
Rule[] rules = mRules; Rule[] rules = mRules;
int len = mRules.length; int len = mRules.length;
for (int i = 0; i < len; i++) { for (int i = 0; i < len; i++) {
@ -670,6 +693,7 @@ public class FastDateFormat extends Format {
return buf; return buf;
} }
// Parsing
//----------------------------------------------------------------------- //-----------------------------------------------------------------------
/** /**
* Parsing not supported. * Parsing not supported.
@ -684,6 +708,7 @@ public class FastDateFormat extends Format {
return null; return null;
} }
// Accessors
//----------------------------------------------------------------------- //-----------------------------------------------------------------------
/** /**
* Gets the pattern used by this formatter. * 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 * Gets the time zone used by this formatter.
* formatted dates is used instead. * <p>
* 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 * @return the time zone
*/ */
@ -704,6 +732,16 @@ public class FastDateFormat extends Format {
return mTimeZone; 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. * Gets the locale used by this formatter.
* *
@ -723,6 +761,56 @@ public class FastDateFormat extends Format {
return mMaxLengthEstimate; 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. * Inner class defining a rule.
@ -809,6 +897,8 @@ public class FastDateFormat extends Format {
* Inner class to output an unpadded number. * Inner class to output an unpadded number.
*/ */
private static class UnpaddedNumberField implements NumberRule { private static class UnpaddedNumberField implements NumberRule {
static final UnpaddedNumberField INSTANCE_YEAR = new UnpaddedNumberField(Calendar.YEAR);
private final int mField; private final int mField;
UnpaddedNumberField(int field) { UnpaddedNumberField(int field) {
@ -826,12 +916,10 @@ public class FastDateFormat extends Format {
public final void appendTo(StringBuffer buffer, int value) { public final void appendTo(StringBuffer buffer, int value) {
if (value < 10) { if (value < 10) {
buffer.append((char)(value + '0')); 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'));
buffer.append((char)(value % 10 + '0')); buffer.append((char)(value % 10 + '0'));
} } else {
else {
buffer.append(Integer.toString(value)); buffer.append(Integer.toString(value));
} }
} }
@ -841,6 +929,8 @@ public class FastDateFormat extends Format {
* Inner class to output an unpadded month. * Inner class to output an unpadded month.
*/ */
private static class UnpaddedMonthField implements NumberRule { private static class UnpaddedMonthField implements NumberRule {
static final UnpaddedMonthField INSTANCE = new UnpaddedMonthField();
UnpaddedMonthField() { UnpaddedMonthField() {
} }
@ -855,8 +945,7 @@ public class FastDateFormat extends Format {
public final void appendTo(StringBuffer buffer, int value) { public final void appendTo(StringBuffer buffer, int value) {
if (value < 10) { if (value < 10) {
buffer.append((char)(value + '0')); buffer.append((char)(value + '0'));
} } else {
else {
buffer.append((char)(value / 10 + '0')); buffer.append((char)(value / 10 + '0'));
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'));
buffer.append((char)(value % 10 + '0')); buffer.append((char)(value % 10 + '0'));
} } else {
else {
int digits; int digits;
if (value < 1000) { if (value < 1000) {
digits = 3; digits = 3;
} } else {
else {
digits = (int)(Math.log(value) / LOG_10) + 1; digits = (int)(Math.log(value) / LOG_10) + 1;
} }
for (int i = mSize; --i >= digits; ) { for (int i = mSize; --i >= digits; ) {
@ -933,8 +1020,7 @@ public class FastDateFormat extends Format {
if (value < 100) { if (value < 100) {
buffer.append((char)(value / 10 + '0')); buffer.append((char)(value / 10 + '0'));
buffer.append((char)(value % 10 + '0')); buffer.append((char)(value % 10 + '0'));
} } else {
else {
buffer.append(Integer.toString(value)); buffer.append(Integer.toString(value));
} }
} }
@ -944,6 +1030,8 @@ public class FastDateFormat extends Format {
* Inner class to output a two digit year. * Inner class to output a two digit year.
*/ */
private static class TwoDigitYearField implements NumberRule { private static class TwoDigitYearField implements NumberRule {
static final TwoDigitYearField INSTANCE = new TwoDigitYearField();
TwoDigitYearField() { TwoDigitYearField() {
} }
@ -965,6 +1053,8 @@ public class FastDateFormat extends Format {
* Inner class to output a two digit month. * Inner class to output a two digit month.
*/ */
private static class TwoDigitMonthField implements NumberRule { private static class TwoDigitMonthField implements NumberRule {
static final TwoDigitMonthField INSTANCE = new TwoDigitMonthField();
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 TimeZone mTimeZone;
private final boolean mTimeZoneForced;
private final Locale mLocale; private final Locale mLocale;
private final int mStyle; private final int mStyle;
private final String mStandard; private final String mStandard;
private final String mDaylight; private final String mDaylight;
TimeZoneRule(TimeZone timeZone, Locale locale, int style) { TimeZoneNameRule(TimeZone timeZone, boolean timeZoneForced, Locale locale, int style) {
mTimeZone = timeZone; mTimeZone = timeZone;
mTimeZoneForced = timeZoneForced;
mLocale = locale; mLocale = locale;
mStyle = style; mStyle = style;
if (timeZone != null) { if (timeZoneForced) {
mStandard = getTimeZoneDisplay(timeZone, false, style, locale); mStandard = getTimeZoneDisplay(timeZone, false, style, locale);
mDaylight = getTimeZoneDisplay(timeZone, true, style, locale); mDaylight = getTimeZoneDisplay(timeZone, true, style, locale);
} } else {
else {
mStandard = null; mStandard = null;
mDaylight = null; mDaylight = null;
} }
} }
public int estimateLength() { public int estimateLength() {
if (mTimeZone != null) { if (mTimeZoneForced) {
return Math.max(mStandard.length(), mDaylight.length()); return Math.max(mStandard.length(), mDaylight.length());
} } else if (mStyle == TimeZone.SHORT) {
else if (mStyle == TimeZone.SHORT) {
return 4; return 4;
} } else {
else {
return 40; return 40;
} }
} }
public void appendTo(StringBuffer buffer, Calendar calendar) { public void appendTo(StringBuffer buffer, Calendar calendar) {
TimeZone timeZone; if (mTimeZoneForced) {
if ((timeZone = mTimeZone) != null) { if (mTimeZone.useDaylightTime() && calendar.get(Calendar.DST_OFFSET) != 0) {
if (timeZone.useDaylightTime() &&
calendar.get(Calendar.DST_OFFSET) != 0) {
buffer.append(mDaylight); buffer.append(mDaylight);
} } else {
else {
buffer.append(mStandard); buffer.append(mStandard);
} }
} } else {
else { TimeZone timeZone = calendar.getTimeZone();
timeZone = calendar.getTimeZone(); if (timeZone.useDaylightTime() && calendar.get(Calendar.DST_OFFSET) != 0) {
if (timeZone.useDaylightTime() && buffer.append(getTimeZoneDisplay(timeZone, true, mStyle, mLocale));
calendar.get(Calendar.DST_OFFSET) != 0) { } else {
buffer.append(getTimeZoneDisplay(timeZone, false, mStyle, mLocale));
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. * 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 * Helper class for creating compound objects. One use for this class is to create a
* hashtable key out of multiple objects. * hashtable key out of multiple objects.
*/ */
private static class Pair implements Comparable, Serializable { private static class Pair {
private final Object mObj1; private final Object mObj1;
private final Object mObj2; private final Object mObj2;
@ -1153,56 +1273,6 @@ public class FastDateFormat extends Format {
mObj2 = obj2; 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) { public boolean equals(Object obj) {
if (this == obj) { if (this == obj) {
return true; return true;

View File

@ -1,7 +1,7 @@
/* ==================================================================== /* ====================================================================
* The Apache Software License, Version 1.1 * 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. * reserved.
* *
* Redistribution and use in source and binary forms, with or without * 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 Henri Yandell
* @author Stephen Colebourne * @author Stephen Colebourne
* @since 2.0 * @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 { 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 */ /** The start time */
private long startTime = -1; private long startTime = -1;
/** The stop time */ /** The stop time */
@ -187,53 +184,13 @@ public class StopWatch {
/** /**
* <p>Gets a summary of the time that the stopwatch recorded as a string.</p> * <p>Gets a summary of the time that the stopwatch recorded as a string.</p>
* *
* <p>The format used is ISO8601, * <p>The format used is ISO8601-like,
* <i>hours</i>:<i>minutes</i>:<i>seconds</i>.<i>milliseconds</i>.</p> * <i>hours</i>:<i>minutes</i>:<i>seconds</i>.<i>milliseconds</i>.</p>
* *
* @return the time as a String * @return the time as a String
*/ */
public String toString() { public String toString() {
return StopWatch.toString(getTime()); return DurationFormatUtils.formatISO(getTime());
}
/**
* <p>Get the time gap as a string.</p>
*
* <p>The format used is ISO8601,
* <i>hours</i>:<i>minutes</i>:<i>seconds</i>.<i>milliseconds</i>.</p>
*
* @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();
} }
} }

View File

@ -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
* <http://www.apache.org/>.
*/
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 <a href="mailto:sergek@lokitech.com">Serge Knystautas</a>
*/
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());
}
}
}

View File

@ -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
* <http://www.apache.org/>.
*/
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 <a href="mailto:sbailliez@apache.org">Stephane Bailliez</a>
* @author <a href="mailto:stefan.bodewig@epost.de">Stefan Bodewig</a>
* @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);
}
}

View File

@ -1,7 +1,7 @@
/* /* ====================================================================
* The Apache Software License, Version 1.1 * 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. * reserved.
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
@ -23,14 +23,14 @@
* Alternately, this acknowlegement may appear in the software itself, * Alternately, this acknowlegement may appear in the software itself,
* if and wherever such third-party acknowlegements normally appear. * 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 * Foundation" must not be used to endorse or promote products derived
* from this software without prior written permission. For written * from this software without prior written permission. For written
* permission, please contact apache@apache.org. * permission, please contact apache@apache.org.
* *
* 5. Products derived from this software may not be called "Apache" * 5. Products derived from this software may not be called "Apache"
* nor may "Apache" appear in their names without prior written * 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 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
@ -53,22 +53,32 @@
*/ */
package org.apache.commons.lang.time; package org.apache.commons.lang.time;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar; 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.Test;
import junit.framework.TestCase; import junit.framework.TestCase;
import junit.framework.TestSuite; import junit.framework.TestSuite;
import junit.textui.TestRunner; import junit.textui.TestRunner;
/** /**
* TestCase for DateUtils. [Relies heavily on code taken from the * Unit tests {@link org.apache.commons.lang.CalendarUtils}.
* DateUtilsTest class of the jakarata-ant project.]
* *
* @author <a href="mailto:sbailliez@apache.org">Stephane Bailliez</a> * @author <a href="mailto:sergek@lokitech.com">Serge Knystautas</a>
* @author <a href="mailto:stefan.bodewig@epost.de">Stefan Bodewig</a>
*/ */
public class DateUtilsTest extends TestCase { 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) { public static void main(String[] args) {
TestRunner.run(suite()); TestRunner.run(suite());
@ -76,69 +86,300 @@ public class DateUtilsTest extends TestCase {
public static Test suite() { public static Test suite() {
TestSuite suite = new TestSuite(DateUtilsTest.class); TestSuite suite = new TestSuite(DateUtilsTest.class);
suite.setName("DateUtils Tests"); suite.setName("CalendarUtilsTest Tests");
return suite; return suite;
} }
public DateUtilsTest(String s) { protected void setUp() throws Exception {
super(s); 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(){ protected void tearDown() throws Exception {
String text = DateUtils.formatElapsedTime(50*1000); super.tearDown();
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);
} }
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(), * Tests various values with the round method
DateUtils.ISO8601_DATETIME_PATTERN); */
assertEquals("2002-02-23T09:11:12", text); 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"); * Tests various values with the trunc method
Calendar cal = Calendar.getInstance(timeZone); */
cal.set(2002,1,23); public void testTrunc() throws Exception {
String text = DateUtils.format(cal.getTime(), assertEquals("trunc year-1 failed",
DateUtils.ISO8601_DATE_PATTERN); dateParser.parse("January 1, 2002"),
assertEquals("2002-02-23", text); 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 * Tests the parse method, which is supposed to handle various strings
TimeZone timeZone = TimeZone.getTimeZone("GMT+1"); * as flexibly as CVS supports.
Calendar cal = Calendar.getInstance(timeZone); */
cal.set(2002,1,23, 21, 11, 12); public void testParse() throws Exception {
String text = DateUtils.format(cal.getTime(), //This is difficult to test since the "now" used in the
DateUtils.ISO8601_TIME_PATTERN); // parse function cannot be controlled. We could possibly control
assertEquals("20:11:12", text); // 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 * Tests the calendar iterator for week ranges
long ms = (20*3600 + 11*60 + 12)*1000; */
String text = DateUtils.format(ms, public void testWeekIterator() throws Exception {
DateUtils.ISO8601_TIME_PATTERN); Calendar now = Calendar.getInstance();
assertEquals("20:11:12", text); 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"); * Tests the calendar iterator for month-based ranges
Calendar cal = Calendar.getInstance(timeZone); */
// should be full moon public void testMonthIterator() throws Exception {
cal.set(2002, 2, 27); Iterator it = DateUtils.getCalendarIterator(date1, DateUtils.RANGE_MONTH_SUNDAY);
assertEquals(4, DateUtils.getPhaseOfMoon(cal)); assertWeekIterator(it,
// should be new moon dateParser.parse("January 27, 2002"),
cal.set(2002, 2, 12); dateParser.parse("March 2, 2002"));
assertEquals(0, DateUtils.getPhaseOfMoon(cal));
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());
} }
} }
}

View File

@ -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
* <http://www.apache.org/>.
*/
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 <a href="mailto:sbailliez@apache.org">Stephane Bailliez</a>
* @author <a href="mailto:stefan.bodewig@epost.de">Stefan Bodewig</a>
* @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));
}
}

View File

@ -54,6 +54,8 @@
package org.apache.commons.lang.time; package org.apache.commons.lang.time;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Locale; import java.util.Locale;
import java.util.TimeZone; import java.util.TimeZone;
@ -67,7 +69,7 @@ import junit.textui.TestRunner;
* *
* @author Sean Schofield * @author Sean Schofield
* @since 2.0 * @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 { public class FastDateFormatTest extends TestCase {
@ -106,9 +108,15 @@ public class FastDateFormatTest extends TestCase {
public void test_getInstance_String() { public void test_getInstance_String() {
FastDateFormat format1 = FastDateFormat.getInstance("MM/DD/yyyy"); FastDateFormat format1 = FastDateFormat.getInstance("MM/DD/yyyy");
FastDateFormat format2 = 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); 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("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() { 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 format3 = FastDateFormat.getInstance("MM/DD/yyyy", TimeZone.getDefault());
FastDateFormat format4 = 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 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 != format2); // -- junit 3.8 version -- assertFalse(format1 == format2);
assertTrue(format1.getTimeZone().equals(TimeZone.getTimeZone("Atlantic/Reykjavik"))); assertEquals(TimeZone.getTimeZone("Atlantic/Reykjavik"), format1.getTimeZone());
assertNull(format2.getTimeZone()); assertEquals(true, format1.getTimeZoneOverridesCalendar());
assertEquals(TimeZone.getDefault(), format2.getTimeZone());
assertEquals(false, format2.getTimeZoneOverridesCalendar());
assertSame(format3, format4); assertSame(format3, format4);
assertTrue(format3 != format5); // -- junit 3.8 version -- assertFalse(format3 == format5); assertTrue(format3 != format5); // -- junit 3.8 version -- assertFalse(format3 == format5);
assertTrue(format4 != format6); // -- junit 3.8 version -- assertFalse(format3 == format5);
} finally { } finally {
Locale.setDefault(realDefaultLocale); Locale.setDefault(realDefaultLocale);
@ -168,10 +180,66 @@ public class FastDateFormatTest extends TestCase {
TimeZone.getDefault(), Locale.GERMANY); TimeZone.getDefault(), Locale.GERMANY);
assertTrue(format1 != format2); // -- junit 3.8 version -- assertNotSame(format1, format2); assertTrue(format1 != format2); // -- junit 3.8 version -- assertNotSame(format1, format2);
assertEquals(format1.getTimeZone(), TimeZone.getTimeZone("Atlantic/Reykjavik")); assertEquals(TimeZone.getTimeZone("Atlantic/Reykjavik"), format1.getTimeZone());
assertNull(format2.getTimeZone()); assertEquals(TimeZone.getDefault(), format2.getTimeZone());
assertEquals(format3.getTimeZone(), TimeZone.getDefault()); assertEquals(TimeZone.getDefault(), format3.getTimeZone());
assertEquals(format3.getTimeZone(), TimeZone.getTimeZone("America/New_York")); 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);
TimeZone.setDefault(realDefaultZone);
}
}
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 { } finally {
Locale.setDefault(realDefaultLocale); Locale.setDefault(realDefaultLocale);

View File

@ -62,7 +62,7 @@ import junit.textui.TestRunner;
* TestCase for StopWatch. * TestCase for StopWatch.
* *
* @author Stephen Colebourne * @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 { public class StopWatchTest extends TestCase {
@ -80,35 +80,6 @@ public class StopWatchTest extends TestCase {
super(s); 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(){ public void testStopWatchSimple(){
StopWatch watch = new StopWatch(); StopWatch watch = new StopWatch();
assertEquals(0, watch.getTime()); assertEquals(0, watch.getTime());

View File

@ -1,7 +1,7 @@
/* ==================================================================== /* ====================================================================
* The Apache Software License, Version 1.1 * 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. * reserved.
* *
* Redistribution and use in source and binary forms, with or without * 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.TestCase;
import junit.framework.TestSuite; import junit.framework.TestSuite;
import junit.textui.TestRunner; import junit.textui.TestRunner;
/** /**
* Test suite for the Time package. * Test suite for the Time package.
* *
* @author Stephen Colebourne * @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 { public class TimeTestSuite extends TestCase {
@ -85,8 +86,9 @@ public class TimeTestSuite extends TestCase {
public static Test suite() { public static Test suite() {
TestSuite suite = new TestSuite(); TestSuite suite = new TestSuite();
suite.setName("Commons-Lang-Time Tests"); suite.setName("Commons-Lang-Time Tests");
suite.addTest(CalendarUtilsTest.suite());
suite.addTest(DateUtilsTest.suite()); suite.addTest(DateUtilsTest.suite());
suite.addTest(DateFormatUtilsTest.suite());
suite.addTest(DurationFormatUtilsTest.suite());
suite.addTest(StopWatchTest.suite()); suite.addTest(StopWatchTest.suite());
suite.addTest(FastDateFormatTest.suite()); suite.addTest(FastDateFormatTest.suite());
return suite; return suite;