View Javadoc

1   /*
2    * $Id: Date.java 497654 2007-01-19 00:21:57Z rgielen $
3    *
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *  http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  package org.apache.struts2.components;
22  
23  import java.io.IOException;
24  import java.io.Writer;
25  import java.text.DateFormat;
26  import java.text.SimpleDateFormat;
27  import java.util.ArrayList;
28  import java.util.Iterator;
29  import java.util.List;
30  
31  import org.apache.commons.logging.Log;
32  import org.apache.commons.logging.LogFactory;
33  import org.apache.struts2.views.annotations.StrutsTag;
34  import org.apache.struts2.views.annotations.StrutsTagAttribute;
35  
36  import com.opensymphony.xwork2.ActionContext;
37  import com.opensymphony.xwork2.TextProvider;
38  import com.opensymphony.xwork2.util.ValueStack;
39  
40  /***
41   * <!-- START SNIPPET: javadoc -->
42   *
43   * Format Date object in different ways.
44   * <p>
45   * The date tag will allow you to format a Date in a quick and easy way.
46   * You can specify a <b>custom format</b> (eg. "dd/MM/yyyy hh:mm"), you can generate
47   * <b>easy readable notations</b> (like "in 2 hours, 14 minutes"), or you can just fall back
48   * on a <b>predefined format</b> with key 'struts.date.format' in your properties file.
49   *
50   * If that key is not defined, it will finally fall back to the default DateFormat.MEDIUM
51   * formatting.
52   *
53   * <b>Note</b>: If the requested Date object isn't found on the stack, a blank will be returned.
54   * </p>
55   *
56   * Configurable attributes are :-
57   * <ul>
58   *    <li>name</li>
59   *    <li>nice</li>
60   *    <li>format</li>
61   * </ul>
62   *
63   * <p/>
64   *
65   * Following how the date component will work, depending on the value of nice attribute
66   * (which by default is false) and the format attribute.
67   *
68   * <p/>
69   *
70   * <b><u>Condition 1: With nice attribute as true</u></b>
71   * <table border="1">
72   *   <tr>
73   *      <td>i18n key</td>
74   *      <td>default</td>
75   *   </tr>
76   *   <tr>
77   *      <td>struts.date.format.past</td>
78   *      <td>{0} ago</td>
79   *   </tr>
80   *   <tr>
81   *      <td>struts.date.format.future</td>
82   *      <td>in {0}</td>
83   *   </tr>
84   *   <tr>
85   *      <td>struts.date.format.seconds</td>
86   *      <td>an instant</td>
87   *   </tr>
88   *   <tr>
89   *      <td>struts.date.format.minutes</td>
90   *      <td>{0,choice,1#one minute|1<{0} minutes}</td>
91   *   </tr>
92   *   <tr>
93   *      <td>struts.date.format.hours</td>
94   *      <td>{0,choice,1#one hour|1<{0} hours}{1,choice,0#|1#, one minute|1<, {1} minutes}</td>
95   *   </tr>
96   *   <tr>
97   *      <td>struts.date.format.days</td>
98   *      <td>{0,choice,1#one day|1<{0} days}{1,choice,0#|1#, one hour|1<, {1} hours}</td>
99   *   </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  *  &lt;s:date name="person.birthday" format="dd/MM/yyyy" /&gt;
132  *  &lt;s:date name="person.birthday" format="%{getText('some.i18n.key')}" /&gt;
133  *  &lt;s:date name="person.birthday" nice="true" /&gt;
134  *  &lt;s:date name="person.birthday" /&gt;
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, DATETAG_DEFAULT_YEARS, args));
238         } else if (day > 0) {
239             args.add(new Long(day));
240             args.add(new Long(hour));
241             args.add(sb);
242             args.add(null);
243             sb.append(tp.getText(DATETAG_PROPERTY_DAYS, DATETAG_DEFAULT_DAYS, args));
244         } else if (hour > 0) {
245             args.add(new Long(hour));
246             args.add(new Long(min));
247             args.add(sb);
248             args.add(null);
249             sb.append(tp.getText(DATETAG_PROPERTY_HOURS, DATETAG_DEFAULT_HOURS, args));
250         } else if (min > 0) {
251             args.add(new Long(min));
252             args.add(new Long(sec));
253             args.add(sb);
254             args.add(null);
255             sb.append(tp.getText(DATETAG_PROPERTY_MINUTES, DATETAG_DEFAULT_MINUTES, args));
256         } else {
257             args.add(new Long(sec));
258             args.add(sb);
259             args.add(null);
260             sb.append(tp.getText(DATETAG_PROPERTY_SECONDS, DATETAG_DEFAULT_SECONDS, args));
261         }
262 
263         args.clear();
264         args.add(sb.toString());
265         if (date.before(now)) {
266             // looks like this date is passed
267             return tp.getText(DATETAG_PROPERTY_PAST, DATETAG_DEFAULT_PAST, args);
268         } else {
269             return tp.getText(DATETAG_PROPERTY_FUTURE, DATETAG_DEFAULT_FUTURE, args);
270         }
271     }
272 
273     public boolean end(Writer writer, String body) {
274         String msg = null;
275         ValueStack stack = getStack();
276         java.util.Date date = null;
277         // find the name on the valueStack, and cast it to a date
278         try {
279             date = (java.util.Date) findValue(name);
280         } catch (Exception e) {
281             LOG.error("Could not convert object with key '" + name
282                     + "' to a java.util.Date instance");
283             // bad date, return a blank instead ?
284             msg = "";
285         }
286 
287         //try to find the format on the stack
288         if (format != null) {
289             format = findString(format);
290         }
291         if (date != null) {
292             TextProvider tp = findProviderInStack();
293             if (tp != null) {
294                 if (nice) {
295                     msg = formatTime(tp, date);
296                 } else {
297                     if (format == null) {
298                         String globalFormat = null;
299 
300                         // if the format is not specified, fall back using the
301                         // defined property DATETAG_PROPERTY
302                         globalFormat = tp.getText(DATETAG_PROPERTY);
303 
304                         // if tp.getText can not find the property then the
305                         // returned string is the same as input =
306                         // DATETAG_PROPERTY
307                         if (globalFormat != null
308                                 && !DATETAG_PROPERTY.equals(globalFormat)) {
309                             msg = new SimpleDateFormat(globalFormat,
310                                     ActionContext.getContext().getLocale())
311                                     .format(date);
312                         } else {
313                             msg = DateFormat.getDateTimeInstance(
314                                     DateFormat.MEDIUM, DateFormat.MEDIUM,
315                                     ActionContext.getContext().getLocale())
316                                     .format(date);
317                         }
318                     } else {
319                         msg = new SimpleDateFormat(format, ActionContext
320                                 .getContext().getLocale()).format(date);
321                     }
322                 }
323                 if (msg != null) {
324                     try {
325                         if (getId() == null) {
326                             writer.write(msg);
327                         } else {
328                             stack.getContext().put(getId(), msg);
329                         }
330                     } catch (IOException e) {
331                         LOG.error("Could not write out Date tag", e);
332                     }
333                 }
334             }
335         }
336         return super.end(writer, "");
337     }
338 
339     @StrutsTagAttribute(description="Date or DateTime format pattern", rtexprvalue=false)
340     public void setFormat(String format) {
341         this.format = format;
342     }
343 
344     @StrutsTagAttribute(description="Whether to print out the date nicely", type="Boolean", defaultValue="false")
345     public void setNice(boolean nice) {
346         this.nice = nice;
347     }
348 
349     /***
350      * @return Returns the name.
351      */
352     public String getName() {
353         return name;
354     }
355 
356     @StrutsTagAttribute(description="The date value to format", required=true)
357     public void setName(String name) {
358         this.name = name;
359     }
360 
361     /***
362      * @return Returns the format.
363      */
364     public String getFormat() {
365         return format;
366     }
367 
368     /***
369      * @return Returns the nice.
370      */
371     public boolean isNice() {
372         return nice;
373     }
374 }