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