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 }