001: /*
002: * Copyright (c) 2002-2006 by OpenSymphony
003: * All rights reserved.
004: */
005: package com.opensymphony.webwork.components;
006:
007: import com.opensymphony.xwork.ActionContext;
008: import com.opensymphony.xwork.TextProvider;
009: import com.opensymphony.xwork.util.OgnlValueStack;
010: import org.apache.commons.logging.Log;
011: import org.apache.commons.logging.LogFactory;
012:
013: import java.io.IOException;
014: import java.io.Writer;
015: import java.text.DateFormat;
016: import java.text.SimpleDateFormat;
017: import java.util.ArrayList;
018: import java.util.Iterator;
019: import java.util.List;
020:
021: /**
022: * <!-- START SNIPPET: javadoc -->
023: *
024: * Format Date object in different ways.
025: * <p>
026: * The date tag will allow you to format a Date in a quick and easy way.
027: * You can specify a <b>custom format</b> (eg. "dd/MM/yyyy hh:mm"), you can generate
028: * <b>easy readable notations</b> (like "in 2 hours, 14 minutes"), or you can just fall back
029: * on a <b>predefined format</b> with key 'webwork.date.format' in your properties file.
030: *
031: * If that key is not defined, it will finally fall back to the default DateFormat.MEDIUM
032: * formatting.
033: *
034: * <b>Note</b>: If the requested Date object isn't found on the stack, a blank will be returned.
035: * </p>
036: *
037: * Configurable attributes are :-
038: * <ul>
039: * <li>name</li>
040: * <li>nice</li>
041: * <li>format</li>
042: * </ul>
043: *
044: * <p/>
045: *
046: * Following how the date component will work, depending on the value of nice attribute
047: * (which by default is false) and the format attribute.
048: *
049: * <p/>
050: *
051: * <b><u>Condition 1: With nice attribute as true</u></b>
052: * <table border="1">
053: * <tr>
054: * <td>i18n key</td>
055: * <td>default</td>
056: * </tr>
057: * <tr>
058: * <td>webwork.date.format.past</td>
059: * <td>{0} ago</td>
060: * </tr>
061: * <tr>
062: * <td>webwork.date.format.future</td>
063: * <td>in {0}</td>
064: * </tr>
065: * <tr>
066: * <td>webwork.date.format.seconds</td>
067: * <td>an instant</td>
068: * </tr>
069: * <tr>
070: * <td>webwork.date.format.minutes</td>
071: * <td>{0,choice,1#one minute|1<{0} minutes}</td>
072: * </tr>
073: * <tr>
074: * <td>webwork.date.format.hours</td>
075: * <td>{0,choice,1#one hour|1<{0} hours}{1,choice,0#|1#, one minute|1<, {1} minutes}</td>
076: * </tr>
077: * <tr>
078: * <td>webwork.date.format.days</td>
079: * <td>{0,choice,1#one day|1<{0} days}{1,choice,0#|1#, one hour|1<, {1} hours}</td>
080: * </tr>
081: * <tr>
082: * <td>webwork.date.format.years</td>
083: * <td>{0,choice,1#one year|1<{0} years}{1,choice,0#|1#, one day|1<, {1} days}</td>
084: * </tr>
085: * </table>
086: *
087: * <p/>
088: *
089: * <b><u>Condition 2: With nice attribute as false and format attribute is specified eg. dd/MM/yyyyy </u></b>
090: * <p>In this case the format attribute will be used.</p>
091: *
092: * <p/>
093: *
094: * <b><u>Condition 3: With nice attribute as false and no format attribute is specified </u></b>
095: * <table border="1">
096: * <tr>
097: * <td>i18n key</td>
098: * <td>default</td>
099: * </tr>
100: * <tr>
101: * <td>webwork.date.format</td>
102: * <td>if one is not found DateFormat.MEDIUM format will be used</td>
103: * </tr>
104: * </table>
105: *
106: *
107: * <!-- END SNIPPET: javadoc -->
108: *
109: * <p/> <b>Examples</b>
110: * <pre>
111: * <!-- START SNIPPET: example -->
112: * <ww:date name="person.birthday" format="dd/MM/yyyy" />
113: * <ww:date name="person.birthday" format="%{getText('some.i18n.key')}" />
114: * <ww:date name="person.birthday" nice="true" />
115: * <ww:date name="person.birthday" />
116: * <!-- END SNIPPET: example -->
117: * </pre>
118: *
119: * <code>Date</code>
120: *
121: * @author <a href="mailto:philip.luppens@gmail.com">Philip Luppens</a>
122: * @author <a href="mailto:hermanns@aixcept.de">Rainer Hermanns</a>
123: * @version $Id: Date.java 2513 2006-03-21 17:26:19Z rainerh $
124: *
125: *
126: * @ww.tag name="date" tld-body-content="empty"
127: * tld-tag-class="com.opensymphony.webwork.views.jsp.DateTag"
128: * description="Render a formatted date."
129: */
130: public class Date extends Component {
131:
132: private static final Log LOG = LogFactory.getLog(Date.class);
133: /**
134: * Property name to fall back when no format is specified
135: */
136: public static final String DATETAG_PROPERTY = "webwork.date.format";
137: /**
138: * Property name that defines the past notation (default: {0} ago)
139: */
140: public static final String DATETAG_PROPERTY_PAST = "webwork.date.format.past";
141: private static final String DATETAG_DEFAULT_PAST = "{0} ago";
142: /**
143: * Property name that defines the future notation (default: in {0})
144: */
145: public static final String DATETAG_PROPERTY_FUTURE = "webwork.date.format.future";
146: private static final String DATETAG_DEFAULT_FUTURE = "in {0}";
147: /**
148: * Property name that defines the seconds notation (default: in instant)
149: */
150: public static final String DATETAG_PROPERTY_SECONDS = "webwork.date.format.seconds";
151: private static final String DATETAG_DEFAULT_SECONDS = "an instant";
152: /**
153: * Property name that defines the minutes notation (default: {0,choice,1#one minute|1<{0} minutes})
154: */
155: public static final String DATETAG_PROPERTY_MINUTES = "webwork.date.format.minutes";
156: private static final String DATETAG_DEFAULT_MINUTES = "{0,choice,1#one minute|1<{0} minutes}";
157: /**
158: * Property name that defines the hours notation (default: {0,choice,1#one hour|1<{0} hours}{1,choice,0#|1#, one
159: * minute|1<, {1} minutes})
160: */
161: public static final String DATETAG_PROPERTY_HOURS = "webwork.date.format.hours";
162: private static final String DATETAG_DEFAULT_HOURS = "{0,choice,1#one hour|1<{0} hours}{1,choice,0#|1#, one minute|1<, {1} minutes}";
163: /**
164: * Property name that defines the days notation (default: {0,choice,1#one day|1<{0} days}{1,choice,0#|1#, one hour|1<,
165: * {1} hours})
166: */
167: public static final String DATETAG_PROPERTY_DAYS = "webwork.date.format.days";
168: private static final String DATETAG_DEFAULT_DAYS = "{0,choice,1#one day|1<{0} days}{1,choice,0#|1#, one hour|1<, {1} hours}";
169: /**
170: * Property name that defines the years notation (default: {0,choice,1#one year|1<{0} years}{1,choice,0#|1#, one
171: * day|1<, {1} days})
172: */
173: public static final String DATETAG_PROPERTY_YEARS = "webwork.date.format.years";
174: private static final String DATETAG_DEFAULT_YEARS = "{0,choice,1#one year|1<{0} years}{1,choice,0#|1#, one day|1<, {1} days}";
175:
176: private String name;
177:
178: private String format;
179:
180: private boolean nice;
181:
182: public Date(OgnlValueStack stack) {
183: super (stack);
184: }
185:
186: private TextProvider findProviderInStack() {
187: for (Iterator iterator = getStack().getRoot().iterator(); iterator
188: .hasNext();) {
189: Object o = iterator.next();
190:
191: if (o instanceof TextProvider) {
192: return (TextProvider) o;
193: }
194: }
195: return null;
196: }
197:
198: /**
199: * Calculates the difference in time from now to the given date, and outputs it nicely. <p/> An example: <br/>Now =
200: * 2006/03/12 13:38:00, date = 2006/03/12 15:50:00 will output "in 1 hour, 12 minutes".
201: *
202: * @param tp text provider
203: * @param date the date
204: * @return the date nicely
205: */
206: public String formatTime(TextProvider tp, java.util.Date date) {
207: java.util.Date now = new java.util.Date();
208: StringBuffer sb = new StringBuffer();
209: List args = new ArrayList();
210: long secs = Math.abs((now.getTime() - date.getTime()) / 1000);
211: long mins = secs / 60;
212: long sec = secs % 60;
213: int min = (int) mins % 60;
214: long hours = mins / 60;
215: int hour = (int) hours % 24;
216: int days = (int) hours / 24;
217: int day = days % 365;
218: int years = days / 365;
219:
220: if (years > 0) {
221: args.add(new Long(years));
222: args.add(new Long(day));
223: args.add(sb);
224: args.add(null);
225: sb.append(tp.getText(DATETAG_PROPERTY_YEARS,
226: DATETAG_DEFAULT_YEARS, args));
227: } else if (day > 0) {
228: args.add(new Long(day));
229: args.add(new Long(hour));
230: args.add(sb);
231: args.add(null);
232: sb.append(tp.getText(DATETAG_PROPERTY_DAYS,
233: DATETAG_DEFAULT_DAYS, args));
234: } else if (hour > 0) {
235: args.add(new Long(hour));
236: args.add(new Long(min));
237: args.add(sb);
238: args.add(null);
239: sb.append(tp.getText(DATETAG_PROPERTY_HOURS,
240: DATETAG_DEFAULT_HOURS, args));
241: } else if (min > 0) {
242: args.add(new Long(min));
243: args.add(new Long(sec));
244: args.add(sb);
245: args.add(null);
246: sb.append(tp.getText(DATETAG_PROPERTY_MINUTES,
247: DATETAG_DEFAULT_MINUTES, args));
248: } else {
249: args.add(new Long(sec));
250: args.add(sb);
251: args.add(null);
252: sb.append(tp.getText(DATETAG_PROPERTY_SECONDS,
253: DATETAG_DEFAULT_SECONDS, args));
254: }
255:
256: args.clear();
257: args.add(sb.toString());
258: if (date.before(now)) {
259: // looks like this date is passed
260: return tp.getText(DATETAG_PROPERTY_PAST,
261: DATETAG_DEFAULT_PAST, args);
262: } else {
263: return tp.getText(DATETAG_PROPERTY_FUTURE,
264: DATETAG_DEFAULT_FUTURE, args);
265: }
266: }
267:
268: public boolean end(Writer writer, String body) {
269: String msg = null;
270: OgnlValueStack stack = getStack();
271: java.util.Date date = null;
272: // find the name on the valueStack, and cast it to a date
273: try {
274: date = (java.util.Date) findValue(name);
275: } catch (Exception e) {
276: LOG.error("Could not convert object with key '" + name
277: + "' to a java.util.Date instance");
278: // bad date, return a blank instead ?
279: msg = "";
280: }
281:
282: //try to find the format on the stack
283: if (format != null) {
284: format = findString(format);
285: }
286: if (date != null) {
287: TextProvider tp = findProviderInStack();
288: if (tp != null) {
289: if (nice) {
290: msg = formatTime(tp, date);
291: } else {
292: if (format == null) {
293: String globalFormat = null;
294:
295: // if the format is not specified, fall back using the
296: // defined property DATETAG_PROPERTY
297: globalFormat = tp.getText(DATETAG_PROPERTY);
298:
299: // if tp.getText can not find the property then the
300: // returned string is the same as input =
301: // DATETAG_PROPERTY
302: if (globalFormat != null
303: && !DATETAG_PROPERTY
304: .equals(globalFormat)) {
305: msg = new SimpleDateFormat(globalFormat,
306: ActionContext.getContext()
307: .getLocale()).format(date);
308: } else {
309: msg = DateFormat.getDateTimeInstance(
310: DateFormat.MEDIUM,
311: DateFormat.MEDIUM,
312: ActionContext.getContext()
313: .getLocale()).format(date);
314: }
315: } else {
316: msg = new SimpleDateFormat(format,
317: ActionContext.getContext().getLocale())
318: .format(date);
319: }
320: }
321: if (msg != null) {
322: try {
323: if (getId() == null) {
324: writer.write(msg);
325: } else {
326: stack.getContext().put(getId(), msg);
327: }
328: } catch (IOException e) {
329: LOG.error("Could not write out Date tag", e);
330: }
331: }
332: }
333: }
334: return super .end(writer, "");
335: }
336:
337: /**
338: * Date or DateTime format pattern
339: *
340: * @ww.tagattribute required="false" rtexprvalue="false"
341: */
342: public void setFormat(String format) {
343: this .format = format;
344: }
345:
346: /**
347: * Whether to print out the date nicely
348: *
349: * @ww.tagattribute required="false" type="Boolean" default="false"
350: */
351: public void setNice(boolean nice) {
352: this .nice = nice;
353: }
354:
355: /**
356: * @return Returns the name.
357: */
358: public String getName() {
359: return name;
360: }
361:
362: /**
363: * The date value to format
364: *
365: * @ww.tagattribute required="true" type="String"
366: */
367: public void setName(String name) {
368: this .name = name;
369: }
370:
371: /**
372: * @return Returns the format.
373: */
374: public String getFormat() {
375: return format;
376: }
377:
378: /**
379: * @return Returns the nice.
380: */
381: public boolean isNice() {
382: return nice;
383: }
384: }
|