/*
* $Id: Date.java 929448 2010-03-31 09:53:00Z lukaszlenart $
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.struts2.components;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.TextProvider;
import com.opensymphony.xwork2.util.ValueStack;
import com.opensymphony.xwork2.util.logging.Logger;
import com.opensymphony.xwork2.util.logging.LoggerFactory;
import org.apache.struts2.views.annotations.StrutsTag;
import org.apache.struts2.views.annotations.StrutsTagAttribute;
import java.io.IOException;
import java.io.Writer;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Iterator;
import java.util.List;
import java.util.TimeZone;
/**
* <!-- START SNIPPET: javadoc -->
*
* Format Date object in different ways.
* <p>
* The date tag will allow you to format a Date in a quick and easy way.
* You can specify a <b>custom format</b> (eg. "dd/MM/yyyy hh:mm"), you can generate
* <b>easy readable notations</b> (like "in 2 hours, 14 minutes"), or you can just fall back
* on a <b>predefined format</b> with key 'struts.date.format' in your properties file.
*
* If that key is not defined, it will finally fall back to the default DateFormat.MEDIUM
* formatting.
*
* <b>Note</b>: If the requested Date object isn't found on the stack, a blank will be returned.
* </p>
*
* Configurable attributes are :-
* <ul>
* <li>name</li>
* <li>nice</li>
* <li>format</li>
* </ul>
*
* <p/>
*
* Following how the date component will work, depending on the value of nice attribute
* (which by default is false) and the format attribute.
*
* <p/>
*
* <b><u>Condition 1: With nice attribute as true</u></b>
* <table border="1">
* <tr>
* <td>i18n key</td>
* <td>default</td>
* </tr>
* <tr>
* <td>struts.date.format.past</td>
* <td>{0} ago</td>
* </tr>
* <tr>
* <td>struts.date.format.future</td>
* <td>in {0}</td>
* </tr>
* <tr>
* <td>struts.date.format.seconds</td>
* <td>an instant</td>
* </tr>
* <tr>
* <td>struts.date.format.minutes</td>
* <td>{0,choice,1#one minute|1<{0} minutes}</td>
* </tr>
* <tr>
* <td>struts.date.format.hours</td>
* <td>{0,choice,1#one hour|1<{0} hours}{1,choice,0#|1#, one minute|1<, {1} minutes}</td>
* </tr>
* <tr>
* <td>struts.date.format.days</td>
* <td>{0,choice,1#one day|1<{0} days}{1,choice,0#|1#, one hour|1<, {1} hours}</td>
* </tr>
* <tr>
* <td>struts.date.format.years</td>
* <td>{0,choice,1#one year|1<{0} years}{1,choice,0#|1#, one day|1<, {1} days}</td>
* </tr>
* </table>
*
* <p/>
*
* <b><u>Condition 2: With nice attribute as false and format attribute is specified eg. dd/MM/yyyyy </u></b>
* <p>In this case the format attribute will be used.</p>
*
* <p/>
*
* <b><u>Condition 3: With nice attribute as false and no format attribute is specified </u></b>
* <table border="1">
* <tr>
* <td>i18n key</td>
* <td>default</td>
* </tr>
* <tr>
* <td>struts.date.format</td>
* <td>if one is not found DateFormat.MEDIUM format will be used</td>
* </tr>
* </table>
*
*
* <!-- END SNIPPET: javadoc -->
*
* <p/> <b>Examples</b>
* <pre>
* <!-- START SNIPPET: example -->
* <s:date name="person.birthday" format="dd/MM/yyyy" />
* <s:date name="person.birthday" format="%{getText('some.i18n.key')}" />
* <s:date name="person.birthday" nice="true" />
* <s:date name="person.birthday" />
* <!-- END SNIPPET: example -->
* </pre>
*
* <code>Date</code>
*
*/
@StrutsTag(name="date", tldBodyContent="empty", tldTagClass="org.apache.struts2.views.jsp.DateTag", description="Render a formatted date.")
public class Date extends ContextBean {
private static final Logger LOG = LoggerFactory.getLogger(Date.class);
/**
* Property name to fall back when no format is specified
*/
public static final String DATETAG_PROPERTY = "struts.date.format";
/**
* Property name that defines the past notation (default: {0} ago)
*/
public static final String DATETAG_PROPERTY_PAST = "struts.date.format.past";
private static final String DATETAG_DEFAULT_PAST = "{0} ago";
/**
* Property name that defines the future notation (default: in {0})
*/
public static final String DATETAG_PROPERTY_FUTURE = "struts.date.format.future";
private static final String DATETAG_DEFAULT_FUTURE = "in {0}";
/**
* Property name that defines the seconds notation (default: in instant)
*/
public static final String DATETAG_PROPERTY_SECONDS = "struts.date.format.seconds";
private static final String DATETAG_DEFAULT_SECONDS = "an instant";
/**
* Property name that defines the minutes notation (default: {0,choice,1#one minute|1<{0} minutes})
*/
public static final String DATETAG_PROPERTY_MINUTES = "struts.date.format.minutes";
private static final String DATETAG_DEFAULT_MINUTES = "{0,choice,1#one minute|1<{0} minutes}";
/**
* Property name that defines the hours notation (default: {0,choice,1#one hour|1<{0} hours}{1,choice,0#|1#, one
* minute|1<, {1} minutes})
*/
public static final String DATETAG_PROPERTY_HOURS = "struts.date.format.hours";
private static final String DATETAG_DEFAULT_HOURS = "{0,choice,1#one hour|1<{0} hours}{1,choice,0#|1#, one minute|1<, {1} minutes}";
/**
* Property name that defines the days notation (default: {0,choice,1#one day|1<{0} days}{1,choice,0#|1#, one hour|1<,
* {1} hours})
*/
public static final String DATETAG_PROPERTY_DAYS = "struts.date.format.days";
private static final String DATETAG_DEFAULT_DAYS = "{0,choice,1#one day|1<{0} days}{1,choice,0#|1#, one hour|1<, {1} hours}";
/**
* Property name that defines the years notation (default: {0,choice,1#one year|1<{0} years}{1,choice,0#|1#, one
* day|1<, {1} days})
*/
public static final String DATETAG_PROPERTY_YEARS = "struts.date.format.years";
private static final String DATETAG_DEFAULT_YEARS = "{0,choice,1#one year|1<{0} years}{1,choice,0#|1#, one day|1<, {1} days}";
private String name;
private String format;
private boolean nice;
private String timezone;
public Date(ValueStack stack) {
super(stack);
}
private TextProvider findProviderInStack() {
for (Iterator iterator = getStack().getRoot().iterator(); iterator
.hasNext();) {
Object o = iterator.next();
if (o instanceof TextProvider) {
return (TextProvider) o;
}
}
return null;
}
/**
* Calculates the difference in time from now to the given date, and outputs it nicely. <p/> An example: <br/>Now =
* 2006/03/12 13:38:00, date = 2006/03/12 15:50:00 will output "in 1 hour, 12 minutes".
*
* @param tp text provider
* @param date the date
* @return the date nicely
*/
public String formatTime(TextProvider tp, java.util.Date date) {
java.util.Date now = new java.util.Date();
StringBuilder sb = new StringBuilder();
List args = new ArrayList();
long secs = Math.abs((now.getTime() - date.getTime()) / 1000);
long mins = secs / 60;
long sec = secs % 60;
int min = (int) mins % 60;
long hours = mins / 60;
int hour = (int) hours % 24;
int days = (int) hours / 24;
int day = days % 365;
int years = days / 365;
if (years > 0) {
args.add(Long.valueOf(years));
args.add(Long.valueOf(day));
args.add(sb);
args.add(null);
sb.append(tp.getText(DATETAG_PROPERTY_YEARS, DATETAG_DEFAULT_YEARS, args));
} else if (day > 0) {
args.add(Long.valueOf(day));
args.add(Long.valueOf(hour));
args.add(sb);
args.add(null);
sb.append(tp.getText(DATETAG_PROPERTY_DAYS, DATETAG_DEFAULT_DAYS, args));
} else if (hour > 0) {
args.add(Long.valueOf(hour));
args.add(Long.valueOf(min));
args.add(sb);
args.add(null);
sb.append(tp.getText(DATETAG_PROPERTY_HOURS, DATETAG_DEFAULT_HOURS, args));
} else if (min > 0) {
args.add(Long.valueOf(min));
args.add(Long.valueOf(sec));
args.add(sb);
args.add(null);
sb.append(tp.getText(DATETAG_PROPERTY_MINUTES, DATETAG_DEFAULT_MINUTES, args));
} else {
args.add(Long.valueOf(sec));
args.add(sb);
args.add(null);
sb.append(tp.getText(DATETAG_PROPERTY_SECONDS, DATETAG_DEFAULT_SECONDS, args));
}
args.clear();
args.add(sb.toString());
if (date.before(now)) {
// looks like this date is passed
return tp.getText(DATETAG_PROPERTY_PAST, DATETAG_DEFAULT_PAST, args);
} else {
return tp.getText(DATETAG_PROPERTY_FUTURE, DATETAG_DEFAULT_FUTURE, args);
}
}
public boolean end(Writer writer, String body) {
String msg = null;
java.util.Date date = null;
// find the name on the valueStack
try {
//suport Calendar also
Object dateObject = findValue(name);
if (dateObject instanceof java.util.Date)
date = (java.util.Date) dateObject;
else if(dateObject instanceof Calendar)
date = ((Calendar) dateObject).getTime();
} catch (Exception e) {
LOG.error("Could not convert object with key '" + name
+ "' to a java.util.Date instance");
// bad date, return a blank instead ?
msg = "";
}
//try to find the format on the stack
if (format != null) {
format = findString(format);
}
if (date != null) {
TextProvider tp = findProviderInStack();
if (tp != null) {
if (nice) {
msg = formatTime(tp, date);
} else {
TimeZone tz = getTimeZone();
if (format == null) {
String globalFormat = null;
// if the format is not specified, fall back using the
// defined property DATETAG_PROPERTY
globalFormat = tp.getText(DATETAG_PROPERTY);
// if tp.getText can not find the property then the
// returned string is the same as input =
// DATETAG_PROPERTY
if (globalFormat != null
&& !DATETAG_PROPERTY.equals(globalFormat)) {
SimpleDateFormat sdf = new SimpleDateFormat(globalFormat,
ActionContext.getContext().getLocale());
sdf.setTimeZone(tz);
msg = sdf.format(date);
} else {
DateFormat df = DateFormat.getDateTimeInstance(
DateFormat.MEDIUM, DateFormat.MEDIUM,
ActionContext.getContext().getLocale());
df.setTimeZone(tz);
msg = df.format(date);
}
} else {
SimpleDateFormat sdf = new SimpleDateFormat(format, ActionContext
.getContext().getLocale());
sdf.setTimeZone(tz);
msg = sdf.format(date);
}
}
if (msg != null) {
try {
if (getVar() == null) {
writer.write(msg);
} else {
putInContext(msg);
}
} catch (IOException e) {
LOG.error("Could not write out Date tag", e);
}
}
}
}
return super.end(writer, "");
}
private TimeZone getTimeZone() {
TimeZone tz = TimeZone.getDefault();
if (timezone != null) {
timezone = stripExpressionIfAltSyntax(timezone);
String actualTimezone = (String) getStack().findValue(timezone, String.class);
if (actualTimezone != null) {
timezone = actualTimezone;
}
tz = TimeZone.getTimeZone(timezone);
}
return tz;
}
@StrutsTagAttribute(description="Date or DateTime format pattern", rtexprvalue=false)
public void setFormat(String format) {
this.format = format;
}
@StrutsTagAttribute(description="Whether to print out the date nicely", type="Boolean", defaultValue="false")
public void setNice(boolean nice) {
this.nice = nice;
}
/**
* @return Returns the name.
*/
public String getName() {
return name;
}
@StrutsTagAttribute(description="The date value to format", required=true)
public void setName(String name) {
this.name = name;
}
/**
* @return Returns the format.
*/
public String getFormat() {
return format;
}
/**
* @return Returns the nice.
*/
public boolean isNice() {
return nice;
}
/**
* @return Returns the name.
*/
public String getTimezone() {
return timezone;
}
@StrutsTagAttribute(description = "The specific timezone in which to format the date", required = false)
public void setTimezone(String timezone) {
this.timezone = timezone;
}
}
|