001    package hirondelle.web4j.config;
002    
003    import java.util.*;
004    import java.text.*;
005    import java.util.regex.*;
006    
007    import hirondelle.web4j.model.DateTime;
008    import hirondelle.web4j.request.DateConverter;
009    
010    /**
011     Implementation of {@link DateConverter}, required by WEB4J. 
012    
013      <P>Dates are formatted in the style, using Jan 31, 2006 at 01:59:59 as an example:
014       <ul>
015         <li>eye-friendly format:  '01/31/2006 01:59:59'; any trailing '00:00:00' or ':' is removed.
016         <li>hand-friendly format: '01312006 01:59:59'; if time portion is absent, 00:00:00 is assumed.
017      </ul>
018    */
019    public final class DateConverterImpl implements DateConverter { 
020    
021      public String formatEyeFriendlyDateTime(DateTime aDateTime, Locale aLocale){
022        return chopOffColon(chopOffMidnight(aDateTime.format("MM/DD/YYYY hh:mm:ss")));
023      }
024      
025      public DateTime parseEyeFriendlyDateTime(String aInputValue, Locale aLocale){
026        return parseDateTime(aInputValue, EYE_FRIENDLY_REGEX);
027      }
028      
029      public DateTime parseHandFriendlyDateTime(String aInputValue, Locale aLocale){
030        return parseDateTime(aInputValue, HAND_FRIENDLY_REGEX);
031      }
032      
033      public String formatEyeFriendly(Date aDate, Locale aLocale, TimeZone aTimeZone) {
034        SimpleDateFormat format = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss"); //HH 00-23
035        format.setTimeZone(aTimeZone);
036        String result = format.format(aDate);
037        return chopOffMidnight(result);
038      }
039      
040      public Date parseHandFriendly(String aInputValue, Locale aLocale, TimeZone aTimeZone) {
041        //if no time portion, then assume 00:00:00
042        return parse(aInputValue, HAND_FRIENDLY_REGEX, aTimeZone);
043      }
044      
045      public Date parseEyeFriendly(String aInputValue, Locale aLocale, TimeZone aTimeZone) {
046        return parse(aInputValue, EYE_FRIENDLY_REGEX, aTimeZone);
047      }
048      
049      // PRIVATE
050      
051      /*
052      * Patterns are thread-safe. Matchers and SimpleDateFormats are NOT thread-safe. 
053      * Items that are not thread-safe can be used only as local variables, not as fields.
054      */
055    
056      /** Month in the Gregorian calendar: <tt>01..12</tt>.   */
057      private static final String MONTH =
058        "(01|02|03|04|05|06|07|08|09|10|11|12)"
059      ;
060    
061      /** Day of the month in the Gregorian calendar: <tt>01..31</tt>.   */
062      private static final String DAY_OF_MONTH = 
063        "(01|02|03|04|05|06|07|08|09|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31)"
064      ;
065       
066      /** Hours in the day <tt>00..23</tt>.  */
067      private static final String HOURS = 
068        "(00|01|02|03|04|05|06|07|08|09|10|11|12|13|14|15|16|17|18|19|20|21|22|23)"
069      ;
070       
071      /** Minutes in an hour <tt>00..59</tt>.  */
072      private static final String MINUTES = 
073        "((?:0|1|2|3|4|5)\\d)"  
074      ;
075    
076      /** Seconds in a minute <tt>00..59</tt>.  */
077      private static final String SECONDS = 
078        "((?:0|1|2|3|4|5)\\d)"  
079      ;
080      
081      /** Format : 01312006 01:59:00   */
082      private static final Pattern HAND_FRIENDLY_REGEX = 
083        Pattern.compile(MONTH + DAY_OF_MONTH + "(\\d\\d\\d\\d)" + "(?: " + HOURS + ":" + MINUTES + ":" + SECONDS +  ")?")
084      ;
085      
086      /** Format : 01/31/2006 01:59:00   */
087      private static final Pattern EYE_FRIENDLY_REGEX = 
088        Pattern.compile(MONTH + "/" + DAY_OF_MONTH + "/" + "(\\d\\d\\d\\d)" + "(?: " + HOURS + ":" + MINUTES + ":" + SECONDS + ")?")
089      ;
090      
091      /**
092      * Requires the month, day, year to be the first, second, and third groups, respectively. 
093      * Optionally, hours, minutes, and seconds can appear as 4th, 5th, and 6th groups, respectively.
094      */
095      private Date parse(String aInputValue, Pattern aRegex, TimeZone aTimeZone){
096        Date result = null;
097        Matcher matcher = aRegex.matcher(aInputValue);
098        if( matcher.matches() ) {
099          Integer month = new Integer(matcher.group(1));
100          Integer day = new Integer(matcher.group(2));
101          Integer year = new Integer( matcher.group(3) );
102          String hour = matcher.group(4);
103          String minute = matcher.group(5);
104          String seconds = matcher.group(6);
105          Calendar cal = null;
106          if( hour == null ){
107            cal = new GregorianCalendar(year.intValue(), month.intValue() - 1, day.intValue(), 0,0,0);
108          }
109          else {
110            Integer hourVal = new Integer(hour);
111            Integer minuteVal = new Integer(minute);
112            Integer secondsVal = new Integer(seconds);
113            cal = new GregorianCalendar(year.intValue(), month.intValue() - 1, day.intValue(), hourVal.intValue(), minuteVal.intValue(), secondsVal.intValue());
114          }
115          cal.setTimeZone(aTimeZone);
116          result = cal.getTime();
117        }
118        return result;
119      }
120    
121      /** Simple 'struct' to hold related items. */
122      private static final class DateTimeParts {
123        String year;
124        String month;
125        String day;
126        String hour;
127        String minute;
128      }
129      
130      private DateTime parseDateTime(String aInputValue, Pattern aRegex){
131        DateTime result = null;
132        Matcher matcher = aRegex.matcher(aInputValue);
133        if( matcher.matches() ) {
134          DateTimeParts parts = getParts(matcher);
135          Integer year = new Integer(parts.year);
136          Integer month = new Integer(parts.month);
137          Integer day = new Integer(parts.day);
138          String hour = parts.hour;
139          String minute = parts.minute;
140          if( hour == null ){
141            result = DateTime.forDateOnly(year, month, day);
142          }
143          else {
144            Integer hourVal = new Integer(hour);
145            Integer minuteVal = new Integer(minute);
146            result = new DateTime(year, month, day, hourVal, minuteVal, null, null);
147          }
148        }
149        return result;
150      }
151    
152      private DateTimeParts getParts(Matcher aMatcher){
153        DateTimeParts result = new DateTimeParts();
154        result.month = aMatcher.group(1);
155        result.day = aMatcher.group(2);
156        result.year = aMatcher.group(3);
157        result.hour = aMatcher.group(4);
158        result.minute = aMatcher.group(5);
159        return result;
160      }
161    
162      private String chopOffMidnight(String aString){
163        return chopOffUnwanted(aString, "00:00:00");
164      }
165      
166      private String chopOffColon(String aString){
167        return chopOffUnwanted(aString, ":");
168      }
169      
170      private String chopOffUnwanted(String aString, String aUnwanted){
171        String result = aString;
172        if ( aString.endsWith(aUnwanted) ) {
173          int end = aString.length() - aUnwanted.length() - 1;
174          result = result.substring(0,end);
175        }
176        return result;
177      }
178      
179    }