View Javadoc

1   /*
2    * $Id: Date.java 454565 2006-10-10 00:02:56Z jmitchell $
3    *
4    * Copyright 2006 The Apache Software Foundation.
5    *
6    * Licensed under the Apache License, Version 2.0 (the "License");
7    * you may not use this file except in compliance with the License.
8    * You may obtain a copy of the License at
9    *
10   *      http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  package org.apache.struts2.components;
19  
20  import java.io.IOException;
21  import java.io.Writer;
22  import java.text.DateFormat;
23  import java.text.SimpleDateFormat;
24  import java.util.ArrayList;
25  import java.util.Iterator;
26  import java.util.List;
27  
28  import org.apache.commons.logging.Log;
29  import org.apache.commons.logging.LogFactory;
30  
31  import com.opensymphony.xwork2.ActionContext;
32  import com.opensymphony.xwork2.TextProvider;
33  import com.opensymphony.xwork2.util.ValueStack;
34  
35  /***
36   * <!-- START SNIPPET: javadoc -->
37   * 
38   * Format Date object in different ways.
39   * <p>
40   * The date tag will allow you to format a Date in a quick and easy way.
41   * You can specify a <b>custom format</b> (eg. "dd/MM/yyyy hh:mm"), you can generate
42   * <b>easy readable notations</b> (like "in 2 hours, 14 minutes"), or you can just fall back
43   * on a <b>predefined format</b> with key 'struts.date.format' in your properties file.
44   *
45   * If that key is not defined, it will finally fall back to the default DateFormat.MEDIUM
46   * formatting.
47   *
48   * <b>Note</b>: If the requested Date object isn't found on the stack, a blank will be returned.
49   * </p>
50   * 
51   * Configurable attributes are :-
52   * <ul>
53   *    <li>name</li>
54   *    <li>nice</li>
55   *    <li>format</li>
56   * </ul>
57   * 
58   * <p/>
59   * 
60   * Following how the date component will work, depending on the value of nice attribute 
61   * (which by default is false) and the format attribute.
62   * 
63   * <p/>
64   * 
65   * <b><u>Condition 1: With nice attribute as true</u></b>
66   * <table border="1">
67   *   <tr>
68   *      <td>i18n key</td>
69   *      <td>default</td>
70   *   </tr>	
71   *   <tr>
72   *   	<td>struts.date.format.past</td>
73   *   	<td>{0} ago</td>
74   *   </tr>
75   *   <tr>
76   *   	<td>struts.date.format.future</td>
77   *      <td>in {0}</td>
78   *   </tr>
79   *   <tr>
80   *   	<td>struts.date.format.seconds</td>
81   *      <td>an instant</td>
82   *   </tr>
83   *   <tr>
84   *   	<td>struts.date.format.minutes</td>
85   *      <td>{0,choice,1#one minute|1<{0} minutes}</td>
86   *   </tr>
87   *   <tr>
88   *   	<td>struts.date.format.hours</td>
89   *   	<td>{0,choice,1#one hour|1<{0} hours}{1,choice,0#|1#, one minute|1<, {1} minutes}</td>
90   *   </tr>
91   *   <tr>
92   *   	<td>struts.date.format.days</td>
93   *   	<td>{0,choice,1#one day|1<{0} days}{1,choice,0#|1#, one hour|1<, {1} hours}</td>
94   *   </tr>
95   *   <tr>
96   *   	<td>struts.date.format.years</td>
97   *   	<td>{0,choice,1#one year|1<{0} years}{1,choice,0#|1#, one day|1<, {1} days}</td>
98   *   </tr>
99   * </table>
100  * 
101  * <p/>
102  * 
103  * <b><u>Condition 2: With nice attribute as false and format attribute is specified eg. dd/MM/yyyyy </u></b>
104  * <p>In this case the format attribute will be used.</p>
105  * 
106  * <p/>
107  * 
108  * <b><u>Condition 3: With nice attribute as false and no format attribute is specified </u></b>
109  * <table border="1">
110  *    <tr>
111  *      <td>i18n key</td>
112  *      <td>default</td>
113  *   </tr>	
114  *   <tr>
115  *   	<td>struts.date.format</td>
116  *      <td>if one is not found DateFormat.MEDIUM format will be used</td>
117  *   </tr>
118  * </table>
119  * 
120  * 
121  * <!-- END SNIPPET: javadoc -->
122  *
123  * <p/> <b>Examples</b>
124  * <pre>
125  *  <!-- START SNIPPET: example -->
126  *  &lt;s:date name="person.birthday" format="dd/MM/yyyy" /&gt;
127  *  &lt;s:date name="person.birthday" format="%{getText('some.i18n.key')}" /&gt;
128  *  &lt;s:date name="person.birthday" nice="true" /&gt;
129  *  &lt;s:date name="person.birthday" /&gt;
130  *  <!-- END SNIPPET: example -->
131  * </pre>
132  *
133  * <code>Date</code>
134  *
135  * @s.tag name="date" tld-body-content="empty"
136  *         tld-tag-class="org.apache.struts2.views.jsp.DateTag"
137  *         description="Render a formatted date."
138  */
139 public class Date extends Component {
140 
141     private static final Log LOG = LogFactory.getLog(Date.class);
142     /***
143      * Property name to fall back when no format is specified
144      */
145     public static final String DATETAG_PROPERTY = "struts.date.format";
146     /***
147      * Property name that defines the past notation (default: {0} ago)
148      */
149     public static final String DATETAG_PROPERTY_PAST = "struts.date.format.past";
150     private static final String DATETAG_DEFAULT_PAST = "{0} ago";
151     /***
152      * Property name that defines the future notation (default: in {0})
153      */
154     public static final String DATETAG_PROPERTY_FUTURE = "struts.date.format.future";
155     private static final String DATETAG_DEFAULT_FUTURE = "in {0}";
156     /***
157      * Property name that defines the seconds notation (default: in instant)
158      */
159     public static final String DATETAG_PROPERTY_SECONDS = "struts.date.format.seconds";
160     private static final String DATETAG_DEFAULT_SECONDS = "an instant";
161     /***
162      * Property name that defines the minutes notation (default: {0,choice,1#one minute|1<{0} minutes})
163      */
164     public static final String DATETAG_PROPERTY_MINUTES = "struts.date.format.minutes";
165     private static final String DATETAG_DEFAULT_MINUTES = "{0,choice,1#one minute|1<{0} minutes}";
166     /***
167      * Property name that defines the hours notation (default: {0,choice,1#one hour|1<{0} hours}{1,choice,0#|1#, one
168      * minute|1<, {1} minutes})
169      */
170     public static final String DATETAG_PROPERTY_HOURS = "struts.date.format.hours";
171     private static final String DATETAG_DEFAULT_HOURS = "{0,choice,1#one hour|1<{0} hours}{1,choice,0#|1#, one minute|1<, {1} minutes}";
172     /***
173      * Property name that defines the days notation (default: {0,choice,1#one day|1<{0} days}{1,choice,0#|1#, one hour|1<,
174      * {1} hours})
175      */
176     public static final String DATETAG_PROPERTY_DAYS = "struts.date.format.days";
177     private static final String DATETAG_DEFAULT_DAYS = "{0,choice,1#one day|1<{0} days}{1,choice,0#|1#, one hour|1<, {1} hours}";
178     /***
179      * Property name that defines the years notation (default: {0,choice,1#one year|1<{0} years}{1,choice,0#|1#, one
180      * day|1<, {1} days})
181      */
182     public static final String DATETAG_PROPERTY_YEARS = "struts.date.format.years";
183     private static final String DATETAG_DEFAULT_YEARS = "{0,choice,1#one year|1<{0} years}{1,choice,0#|1#, one day|1<, {1} days}";
184 
185     private String name;
186 
187     private String format;
188 
189     private boolean nice;
190 
191     public Date(ValueStack stack) {
192         super(stack);
193     }
194 
195     private TextProvider findProviderInStack() {
196         for (Iterator iterator = getStack().getRoot().iterator(); iterator
197                 .hasNext();) {
198             Object o = iterator.next();
199 
200             if (o instanceof TextProvider) {
201                 return (TextProvider) o;
202             }
203         }
204         return null;
205     }
206 
207     /***
208      * Calculates the difference in time from now to the given date, and outputs it nicely. <p/> An example: <br/>Now =
209      * 2006/03/12 13:38:00, date = 2006/03/12 15:50:00 will output "in 1 hour, 12 minutes".
210      *
211      * @param tp   text provider
212      * @param date the date
213      * @return the date nicely
214      */
215     public String formatTime(TextProvider tp, java.util.Date date) {
216         java.util.Date now = new java.util.Date();
217         StringBuffer sb = new StringBuffer();
218         List args = new ArrayList();
219         long secs = Math.abs((now.getTime() - date.getTime()) / 1000);
220         long mins = secs / 60;
221         long sec = secs % 60;
222         int min = (int) mins % 60;
223         long hours = mins / 60;
224         int hour = (int) hours % 24;
225         int days = (int) hours / 24;
226         int day = days % 365;
227         int years = days / 365;
228 
229         if (years > 0) {
230             args.add(new Long(years));
231             args.add(new Long(day));
232             args.add(sb);
233             args.add(null);
234             sb.append(tp.getText(DATETAG_PROPERTY_YEARS, DATETAG_DEFAULT_YEARS, args));
235         } else if (day > 0) {
236             args.add(new Long(day));
237             args.add(new Long(hour));
238             args.add(sb);
239             args.add(null);
240             sb.append(tp.getText(DATETAG_PROPERTY_DAYS, DATETAG_DEFAULT_DAYS, args));
241         } else if (hour > 0) {
242             args.add(new Long(hour));
243             args.add(new Long(min));
244             args.add(sb);
245             args.add(null);
246             sb.append(tp.getText(DATETAG_PROPERTY_HOURS, DATETAG_DEFAULT_HOURS, args));
247         } else if (min > 0) {
248             args.add(new Long(min));
249             args.add(new Long(sec));
250             args.add(sb);
251             args.add(null);
252             sb.append(tp.getText(DATETAG_PROPERTY_MINUTES, DATETAG_DEFAULT_MINUTES, args));
253         } else {
254             args.add(new Long(sec));
255             args.add(sb);
256             args.add(null);
257             sb.append(tp.getText(DATETAG_PROPERTY_SECONDS, DATETAG_DEFAULT_SECONDS, args));
258         }
259 
260         args.clear();
261         args.add(sb.toString());
262         if (date.before(now)) {
263             // looks like this date is passed
264             return tp.getText(DATETAG_PROPERTY_PAST, DATETAG_DEFAULT_PAST, args);
265         } else {
266             return tp.getText(DATETAG_PROPERTY_FUTURE, DATETAG_DEFAULT_FUTURE, args);
267         }
268     }
269 
270     public boolean end(Writer writer, String body) {
271         String msg = null;
272         ValueStack stack = getStack();
273         java.util.Date date = null;
274         // find the name on the valueStack, and cast it to a date
275         try {
276             date = (java.util.Date) findValue(name);
277         } catch (Exception e) {
278             LOG.error("Could not convert object with key '" + name
279                     + "' to a java.util.Date instance");
280             // bad date, return a blank instead ?
281             msg = "";
282         }
283 
284         //try to find the format on the stack
285         if (format != null) {
286             format = findString(format);
287         }
288         if (date != null) {
289             TextProvider tp = findProviderInStack();
290             if (tp != null) {
291                 if (nice) {
292                     msg = formatTime(tp, date);
293                 } else {
294                     if (format == null) {
295                         String globalFormat = null;
296 
297                         // if the format is not specified, fall back using the
298                         // defined property DATETAG_PROPERTY
299                         globalFormat = tp.getText(DATETAG_PROPERTY);
300 
301                         // if tp.getText can not find the property then the
302                         // returned string is the same as input =
303                         // DATETAG_PROPERTY
304                         if (globalFormat != null
305                                 && !DATETAG_PROPERTY.equals(globalFormat)) {
306                             msg = new SimpleDateFormat(globalFormat,
307                                     ActionContext.getContext().getLocale())
308                                     .format(date);
309                         } else {
310                             msg = DateFormat.getDateTimeInstance(
311                                     DateFormat.MEDIUM, DateFormat.MEDIUM,
312                                     ActionContext.getContext().getLocale())
313                                     .format(date);
314                         }
315                     } else {
316                         msg = new SimpleDateFormat(format, ActionContext
317                                 .getContext().getLocale()).format(date);
318                     }
319                 }
320                 if (msg != null) {
321                     try {
322                         if (getId() == null) {
323                             writer.write(msg);
324                         } else {
325                             stack.getContext().put(getId(), msg);
326                         }
327                     } catch (IOException e) {
328                         LOG.error("Could not write out Date tag", e);
329                     }
330                 }
331             }
332         }
333         return super.end(writer, "");
334     }
335 
336     /***
337      * Date or DateTime format pattern
338      *
339      * @s.tagattribute required="false" rtexprvalue="false"
340      */
341     public void setFormat(String format) {
342         this.format = format;
343     }
344 
345     /***
346      * Whether to print out the date nicely
347      *
348      * @s.tagattribute required="false" type="Boolean" default="false"
349      */
350     public void setNice(boolean nice) {
351         this.nice = nice;
352     }
353 
354     /***
355      * @return Returns the name.
356      */
357     public String getName() {
358         return name;
359     }
360 
361     /***
362      * The date value to format
363      *
364      * @s.tagattribute required="true" type="String"
365      */
366     public void setName(String name) {
367         this.name = name;
368     }
369 
370     /***
371      * @return Returns the format.
372      */
373     public String getFormat() {
374         return format;
375     }
376 
377     /***
378      * @return Returns the nice.
379      */
380     public boolean isNice() {
381         return nice;
382 	}
383 }