View Javadoc

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