1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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 * <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, 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
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
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
284 msg = "";
285 }
286
287
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
301
302 globalFormat = tp.getText(DATETAG_PROPERTY);
303
304
305
306
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 }