1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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 * <s:date name="person.birthday" format="dd/MM/yyyy" />
127 * <s:date name="person.birthday" format="%{getText('some.i18n.key')}" />
128 * <s:date name="person.birthday" nice="true" />
129 * <s:date name="person.birthday" />
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
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
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
281 msg = "";
282 }
283
284
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
298
299 globalFormat = tp.getText(DATETAG_PROPERTY);
300
301
302
303
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 }