|
Size: 26443
Comment: fix bad message key and add updated messages.properties
|
← Revision 5 as of 2009-09-20 22:06:23 ⇥
Size: 26443
Comment: converted to 1.6 markup
|
| No differences found! | |
Initial Rough Version
ExtendedDateTool handles requirements for expressing a specified date relative to now as a string in a "friendly format" (e.g. "3 minutes ago", "tomorrow", "3 days from now")
Below is the code for a first run through at making a tool to cover these requirements. This version is still very rough around the edges.
Code consists of:
- A tool for the toolbox
/*
* Copyright 2003-2004 The Apache Software Foundation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.velocity.tools.generic;
import java.util.Calendar;
import org.apache.velocity.tools.generic.DateTool;
/**
* Extension to Velocity DateTool which provides methods for expressing
* Calendar objects in a human-friendly, relative format
*
* @author <a href="mailto:c.townson@nature.com">Christopher Townson</a>
*
* TODO externalize unit conversion methods to dedicated tool?
* TODO better handling of strings/pluralisation/i8n?
* TODO integration of properties with velocity.properties
* TODO clean up problematic handling of dates when time period to be expressed
* is not suited to "friendly-format"
*/
public class ExtendedDateTool extends DateTool
{
/**
* number of milliseconds in a second
*/
public static final long MILLIS_TO_SECOND = 1000;
/**
* number of millseconds in a minute
*/
public static final long MILLIS_TO_MINUTE = 60000;
/**
* number of milliseconds in an hour
*/
public static final long MILLIS_TO_HOUR = 3600000;
/**
* number of milliseconds in a day
*/
public static final long MILLIS_TO_DAY = 86400000;
/**
* String to append to dates which are _after_ the current date
*/
public static final String APPEND_FUTURE =
Messages.getString("ExtendedDateTool.string.date.append.future");
/**
* String to append to dates which are _before_ the current date
*/
public static final String APPEND_PAST =
Messages.getString("ExtendedDateTool.string.date.append.past");
/**
* Strings for unit: seconds (singular)
*/
public static final String UNIT_SECOND =
Messages.getString("ExtendedDateTool.string.unit.seconds.singular");
/**
* String for unit seconds (plural)
*/
public static final String UNIT_SECONDS =
Messages.getString("ExtendedDateTool.string.unit.seconds.plural");
/**
* String for unit: minutes (singular)
*/
public static final String UNIT_MINUTE =
Messages.getString("ExtendedDateTool.string.unit.minutes.singular");
/**
* String for unit minutes (plural)
*/
public static final String UNIT_MINUTES =
Messages.getString("ExtendedDateTool.string.unit.minutes.plural");
/**
* String for unit: minutes (singular)
*/
public static final String UNIT_HOUR =
Messages.getString("ExtendedDateTool.string.unit.hours.singular");
/**
* String for unit minutes (plural)
*/
public static final String UNIT_HOURS =
Messages.getString("ExtendedDateTool.string.unit.hours.plural");
/**
* String for unit: days (singular)
*/
public static final String UNIT_DAY =
Messages.getString("ExtendedDateTool.string.unit.days.singular");
/**
* String for unit days (plural)
*/
public static final String UNIT_DAYS =
Messages.getString("ExtendedDateTool.string.unit.days.plural");
/**
* String for unit: 1 day into the future
*/
public static final String YESTERDAY =
Messages.getString("ExtendedDateTool.string.date.yesterday");
/**
* String for unit: 1 day into the past
*/
public static final String TOMORROW =
Messages.getString("ExtendedDateTool.string.date.tomorrow");
/**
* String for unit: weeks (singular)
*
* Not currently in use as any period over 7 days is better displayed in a
* standard date format
*/
public static final String UNIT_WEEK =
Messages.getString("ExtendedDateTool.string.unit.weeks.singular");
/**
* String for unit: weeks (plural)
*
* Currently only used to determine the fact that date should be
* displayed in a standard format
*/
public static final String UNIT_WEEKS =
Messages.getString("ExtendedDateTool.string.unit.weeks.plural");
/**
* Default output format for dates which are too distant to be suitable
* for rendering in "friendly format"
*/
public static final String DEFAULT_FORMAT =
Messages.getString("ExtendedDateTOol.string.date.format.default");
/**
* Returns a provided Date instance in a human-friendly String format
*
* @param then The Calendar object to convert to human-friendly format
* @return The date in a human-friendly format (e.g. 3 days ago)
*/
public String getFriendlyDate(Calendar then)
{
String friendlyDate = null;
String append = null;
// get the difference between the now and then as a long
long diff = getDifferenceBetweenNowAnd(then);
// is Calendar in the past or the future?
if (isPast(diff))
{
diff -= (diff * 2);
append = APPEND_PAST;
}
else
{
append = APPEND_FUTURE;
}
if (isSeconds(diff))
{
if (isSingular(diff))
{
friendlyDate = convertMillisToSeconds(diff) +
" " + UNIT_SECOND + " " + append;
}
else
{
friendlyDate = convertMillisToSeconds(diff) +
" " + UNIT_SECONDS + " " + append;
}
}
else if (isMinutes(diff))
{
if (isSingular(diff))
{
friendlyDate = convertMillisToMinutes(diff) +
" " + UNIT_MINUTE + " " + append;
}
else
{
friendlyDate = convertMillisToMinutes(diff) +
" " + UNIT_MINUTES + " " + append;
}
}
else if (isHours(diff))
{
if (isSingular(diff))
{
friendlyDate = convertMillisToHours(diff) +
" " + UNIT_HOUR + " " + append;
}
else
{
friendlyDate = convertMillisToHours(diff) +
" " + UNIT_HOURS + " " + append;
}
}
else if (isDays(diff))
{
if (isSingular(diff) && append == APPEND_FUTURE)
{
friendlyDate = TOMORROW;
}
else if(isSingular(diff) && append == APPEND_PAST)
{
friendlyDate = YESTERDAY;
}
else
{
friendlyDate = convertMillisToDays(diff) +
" " + UNIT_DAYS + " " + append;
}
}
else
{
friendlyDate = super.format(DEFAULT_FORMAT,then.getTime());
}
// return friendly string
return friendlyDate;
}
/**
* Overloaded getFriendlyDate method for convenience from templates
*
* NB. The date format descriptor only applies to the date as it will be
* returned if it is <strong>not</strong> suitable for friendly format
*
* @param format
* @param obj
* @return The date in a human-friendly format (e.g. 3 days ago)
*/
public String getFriendlyDate(String format, Object obj) {
return getFriendlyDate(super.toCalendar(super.toDate(format,obj)));
}
/**
* Returns a Java timestamp (milliseconds since 1970-01-01)
*
* @return long java timestamp for the current date
*/
public long getTimeInMillis()
{
return Calendar.getInstance().getTimeInMillis();
}
/**
* Returns a Java timestamp for the supplied Calendar
*
* @param cal
* @return long the Java timestamp for the supplied calendar
*/
public long getTimeInMillis(Calendar cal)
{
return cal.getTimeInMillis();
}
/**
* Returns the time difference between two points expressed in milliseconds
*
* @param pointA
* @param pointB
* @return The time difference in milliseconds
*/
public long getDifferenceBetween(Calendar pointA, Calendar pointB)
{
return pointA.getTimeInMillis() - pointB.getTimeInMillis();
}
/**
* Tests whether the difference between two Calendar instances is
* a matter of seconds or not
*
* @param then The calendar instance for comparison
* @return The difference in milliseconds between now and supplied Calendar
* Returns a positive long if then is after now and a negative long
* if then is before now
*/
public long getDifferenceBetweenNowAnd(Calendar then)
{
return getDifferenceBetween(then, Calendar.getInstance());
}
/**
* COnvert a duration expressed in milliseconds to seconds
*
* @param millis
* @return milliseconds expressed as seconds
*/
public long convertMillisToSeconds(long millis)
{
return millis / MILLIS_TO_SECOND;
}
/**
* Convert a duration expressed in milliseconds to minutes
*
* @param millis
* @return milliseconds expressed as minutes
*/
public long convertMillisToMinutes(long millis)
{
return millis / MILLIS_TO_MINUTE;
}
/**
* Convert a duration expressed in milliseconds to hours
*
* @param millis
* @return milliseconds expressed as hours
*/
public long convertMillisToHours(long millis)
{
return millis / MILLIS_TO_HOUR;
}
/**
* Convert a duration expressed in milliseconds to days
*
* @param millis
* @return milliseconds expressed as days
*/
public long convertMillisToDays(long millis)
{
return millis / MILLIS_TO_DAY;
}
/**
* Tests for positive or negative result from timestamp comparison
*
* @param diff
* @return whether or not our compared time was in the past
*/
private boolean isPast(long diff)
{
if (diff < 0)
{
return true;
}
else
{
return false;
}
}
/**
* Returns the appropriate time unit string for expressing a
* millsecond duration in a "friendly" date format
*
* @param diff
* @return The time unit name
*/
private String calculateUnit(long diff)
{
if (diff > 0 && diff < 60000)
{
return UNIT_SECONDS;
}
else if (diff >= 60000 && diff < 3600000)
{
return UNIT_MINUTES;
}
else if (diff >= 3600000 && diff < 86400000)
{
return UNIT_HOURS;
}
else if (diff >= 86400000 && diff < 604800000)
{
return UNIT_DAYS;
}
else if (diff >= 604800000)
{
return UNIT_WEEKS;
}
else
{
// default
return null;
}
}
/**
* Returns true if the time unit string is singular; false if the string
* needs to be pluralised
*
* This method will only work on positive longs.
*
* @param diff
* @return Whether or not to pluralise the time unit string
*/
private boolean isSingular(long diff)
{
// run this test now to save repeating in conditionals
String units = calculateUnit(diff);
if (units == UNIT_SECONDS && diff < 2000)
{
return true;
}
else if (units == UNIT_MINUTES && diff < 120000)
{
return true;
}
else if (units == UNIT_HOURS && diff < 7200000)
{
return true;
}
else if (units == UNIT_DAYS && diff < 172800000)
{
return true;
}
else if (units == UNIT_WEEKS && diff < 1209600000)
{
return true;
}
else
{
// default
return false;
}
}
/**
* Do we want to talk in seconds?
*
* @param diff
* @return Whether we are dealing with seconds or not
*/
private boolean isSeconds(long diff)
{
if (calculateUnit(diff) == UNIT_SECONDS)
{
return true;
}
else
{
return false;
}
}
/**
* Do we want to talk in minutes?
*
* @param diff
* @return Whether we are dealing with minutes or not
*/
private boolean isMinutes(long diff)
{
if (calculateUnit(diff) == UNIT_MINUTES)
{
return true;
}
else
{
return false;
}
}
/**
* Do we want to talk in hours?
*
* @param diff
* @return Whether we are dealing with hours or not
*/
private boolean isHours(long diff)
{
if (calculateUnit(diff) == UNIT_HOURS)
{
return true;
}
else
{
return false;
}
}
/**
* Do we want to talk in days?
*
* @param diff
* @return Whether we are dealing with days or not
*/
private boolean isDays(long diff)
{
if (calculateUnit(diff) == UNIT_DAYS)
{
return true;
}
else
{
return false;
}
}
/**
* Do we want to talk in weeks?
*
* @param diff
* @return Whether we are dealing with weeks or not
*/
private boolean isWeeks(long diff)
{
if (calculateUnit(diff) == UNIT_WEEKS)
{
return true;
}
else
{
return false;
}
}
}- A default Eclipse messages handler
/**
*
*/
package org.apache.velocity.tools.generic;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
/**
* Eclipse auto-generated helper class to retrieve localised Strings
* for ExtendedDateTool
*
* @author Christopher Townson
*/
public class Messages {
/**
* The bundle name
*/
private static final String BUNDLE_NAME = "org.apache.velocity.tools.generic.messages"; //$NON-NLS-1$
/**
* The resource bundle
*/
private static final ResourceBundle RESOURCE_BUNDLE = ResourceBundle.getBundle(BUNDLE_NAME);
/**
* Default constructor
*
*/
private Messages() {
// do nothing
}
/**
* Accessor method for retrieving String properties
*
* @param key The key for the String property to get
* @return The string property
*/
public static String getString(String key) {
// TODO Auto-generated method stub
try {
return RESOURCE_BUNDLE.getString(key);
} catch (MissingResourceException e) {
return '!' + key + '!';
}
}
}- A messages.properties file
ExtendedDateTool.string.date.append.future=from now ExtendedDateTool.string.date.append.past=ago ExtendedDateTool.string.unit.seconds.singular=second ExtendedDateTool.string.unit.seconds.plural=seconds ExtendedDateTool.string.unit.minutes.singular=minute ExtendedDateTool.string.unit.minutes.plural=minutes ExtendedDateTool.string.unit.hours.singular=hour ExtendedDateTool.string.unit.hours.plural=hours ExtendedDateTool.string.unit.days.singular=day ExtendedDateTool.string.unit.days.plural=days ExtendedDateTool.string.date.yesterday=yesterday ExtendedDateTool.string.date.tomorrow=tomorrow ExtendedDateTool.string.unit.weeks.singular=week ExtendedDateTool.string.unit.weeks.plural=weeks ExtendedDateTool.string.date.format.default=yyyy-MM-dd HH:mm:ss
- A toolbox.xml entry
<!-- ExtendedDateTool: additional date manipulation methods -->
<tool>
<key>xDateTool</key>
<scope>request</scope>
<class>org.apache.velocity.tools.generic.ExtendedDateTool</class>
</tool>
Refactored Version
Nathan has done a lot of refactoring on that initial rough version which has resulted in the following code:
/*
* Copyright 2006 The Apache Software Foundation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.velocity.tools.generic;
import java.util.Calendar;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import org.apache.velocity.tools.generic.DateTool;
/**
* Extension to Velocity DateTool which provides methods for expressing
* date objects in a human-friendly, relative format
*
* @author <a href="mailto:c.townson@nature.com">Christopher Townson</a>
*/
public class ExtendedDateTool extends DateTool
{
/** The number of milliseconds in a second. */
public static final long MILLIS_PER_SECOND = 1000;
/** The number of millseconds in a minute. */
public static final long MILLIS_PER_MINUTE = 60 * MILLIS_PER_SECOND;
/** The number of milliseconds in an hour. */
public static final long MILLIS_PER_HOUR = 60 * MILLIS_PER_MINUTE;
/** The number of milliseconds in a day. */
public static final long MILLIS_PER_DAY = 24 * MILLIS_PER_HOUR;
/** The number of milliseconds in a week. */
public static final long MILLIS_PER_WEEK = 7 * MILLIS_PER_DAY;
/** An approximation of the number of milliseconds in a month. */
public static final long MILLIS_PER_MONTH = 30 * MILLIS_PER_DAY;
/** An approximation of the number of milliseconds in a year. */
public static final long MILLIS_PER_YEAR = 365 * MILLIS_PER_DAY;
/** Array of all "millis per X" values. */
protected static final long[] RELATIVE_UNITS = new long[] {
1, // millis per milli
MILLIS_PER_SECOND,
MILLIS_PER_MINUTE,
MILLIS_PER_HOUR,
MILLIS_PER_DAY,
MILLIS_PER_WEEK,
MILLIS_PER_MONTH,
MILLIS_PER_YEAR };
/** Array of all message keys for relative date units. */
protected static final String[] RELATIVE_UNIT_KEYS = new String[] {
"date.milliseconds",
"date.seconds",
"date.minutes",
"date.hours",
"date.days",
"date.weeks",
"date.months",
"date.years" };
private static final String DEFAULT_RESOURCE_NAME =
"org.apache.velocity.tools.generic.messages";
private ResourceBundle textResources;
protected String getText(String key)
{
if (textResources == null)
{
textResources =
ResourceBundle.getBundle(DEFAULT_RESOURCE_NAME, getLocale());
}
try
{
return textResources.getString(key);
}
catch (MissingResourceException e)
{
return '!' + key + '!';
}
}
/**
* Returns the value of the largest unit difference between the specified
* date and the result of getCalendar() in a human-friendly String format.
*
* @param date The date to convert to human-friendly format
* @return the diff between the specified date and "now" in a
* human-friendly format (e.g. 3 days ago)
*/
public String toRelativeString(Object date)
{
String friendly = toRelativeString(date, 1);
// check for the corner case of "1 day ago" or "1 day from now"
// and convert those to "yesterday" or "tomorrow"
if (friendly != null && friendly.startsWith("1 "+getText("date.days.singular")))
{
if (friendly.endsWith(getText("date.direction.past")))
{
return getText("date.days.singular.past");
}
else
{
return getText("date.days.plural.future");
}
}
return friendly;
}
/**
* Returns the difference between the specified date
* and the result of getCalendar() in a human-friendly String format
* with up to the specified number of units.
*
* @param date The date to convert to human-friendly format
* @param maxUnitDepth The maximum number of units deep to show
* @return the diff between the specified date and "now" in a
* human-friendly format (e.g. 3 days 4 hours 2 seconds ago)
*/
public String toRelativeString(Object date, int maxUnitDepth)
{
return toRelativeString(date, getCalendar(), maxUnitDepth);
}
/**
* Returns the value of the largest unit difference between the specified
* dates in a human-friendly String format.
*
* @param dateA The primary date to be compared
* @param dateB The secondary date to be compared
* @return the diff between the specified date and "now" in a
* human-friendly format (e.g. 3 days ago)
*/
public String toRelativeString(Object dateA, Object dateB)
{
return toRelativeString(dateA, dateB, 1);
}
/**
* Returns the value of the largest unit difference between the specified
* dates in a human-friendly String format with up to the specified number
* of units.
*
* @param dateA The primary date to be compared
* @param dateB The secondary date to be compared
* @param maxUnitDepth The maximum number of units deep to show
* @return the diff between the specified date and "now" in a
* human-friendly format (e.g. 3 days 4 hours 2 seconds ago)
*/
public String toRelativeString(Object dateA, Object dateB,
int maxUnitDepth)
{
// get the difference between the date and now
Long difference = diff(dateA, dateB);
if (difference == null)
{
return null;
}
long diff = difference.longValue();
// is the dateA before or after dateB
boolean isPast = (diff < 0);
if (isPast)
{
// work only with future values
diff *= -1;
}
// get the positive friendly value
String friendly = toRelativeString(diff, maxUnitDepth);
// then add the direction
if (isPast)
{
friendly += " " + getText("date.direction.past");
}
else
{
friendly += " " + getText("date.direction.future");
}
return friendly;
}
/**
* Converts the specified duration of milliseconds into larger units up to
* the specified number of positive units, beginning with the largest
* positive unit. e.g.
* <code>toRelativeString(181453, 3)</code> will return
* "3 minutes 1 second 453 milliseconds",
* <code>toRelativeString(181453, 2)</code> will return
* "3 minutes 1 second", and
* <code>toRelativeString(180000, 2)</code> will return
* "3 minutes".
*/
protected String toRelativeString(long diff, int maxUnitDepth)
{
if (diff < 0)
{
return null;
}
if (diff == 0)
{
//TODO? should we differentiate between "now" and "same time"?
return getText("date.now");
}
long value = 0;
long remainder = 0;
String unitKey = null;
// determine the largest unit and calculate the value and remainder
for (int i = 0; i < RELATIVE_UNITS.length + 1; i++)
{
// if diff < MILLIS_PER_MINUTE or we're at the end
if (diff < RELATIVE_UNITS[i] || i > RELATIVE_UNITS.length)
{
// then we're working with seconds
value = diff / RELATIVE_UNITS[i - 1];
remainder = diff - (value * RELATIVE_UNITS[i - 1]);
unitKey = RELATIVE_UNIT_KEYS[i - 1];
break;
}
}
// select proper pluralization
if (value == 1)
{
unitKey += ".singular";
}
else
{
unitKey += ".plural";
}
// combine the value and the unit
String output = value + ' ' + getText(unitKey);
// recurse over the remainder if it exists and more units are allowed
if (maxUnitDepth > 1 && remainder > 0)
{
output += " " + toRelativeString(remainder, maxUnitDepth - 1);
}
return output;
}
/**
* Returns the time difference between two dates expressed in milliseconds
*
* @param dateA
* @param dateB
* @return The time difference in milliseconds
*/
public Long diff(Object dateA, Object dateB)
{
Calendar calA = toCalendar(dateA);
Calendar calB = toCalendar(dateB);
if (calA == null || calB == null)
{
return null;
}
return new Long(calA.getTimeInMillis() - calB.getTimeInMillis());
}
/**
* @param date The date for comparison
* @return The difference in milliseconds between the supplied date and
* the date returned by getCalendar() (i.e. "now"). Returns a
* positive long if then is after now and a negative long if then
* is before now or <code>null</code> if the parameter can not be
* converted to a Calendar.
*/
public Long diff(Object date)
{
return diff(date, getCalendar());
}
}- And the following updated messages.properties
date.direction.future=from now date.direction.past=ago date.now=now date.milliseconds.singular=millisecond date.milliseconds.plural=milliseconds date.seconds.singular=second date.seconds.plural=seconds date.minutes.singular=minute date.minutes.plural=minutes date.hours.singular=hour date.hours.plural=hours date.days.singular=day date.days.singular.past=tomorrow date.days.singular.future=yesterday date.days.plural=days date.weeks.singular=week date.weeks.plural=weeks date.months.singular=month date.months.plural=months date.years.singular=year date.years.plural=years