1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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 * <s:date name="person.birthday" format="dd/MM/yyyy" />
134 * <s:date name="person.birthday" format="%{getText('some.i18n.key')}" />
135 * <s:date name="person.birthday" nice="true" />
136 * <s:date name="person.birthday" />
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
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
280 try {
281
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
291 msg = "";
292 }
293
294
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
308
309 globalFormat = tp.getText(DATETAG_PROPERTY);
310
311
312
313
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 }