classes/hirondelle/web4j/model/DateTimeParser.java
changeset 0 3060119b1292
equal deleted inserted replaced
-1:000000000000 0:3060119b1292
       
     1 package hirondelle.web4j.model;
       
     2 
       
     3 import java.util.regex.Matcher;
       
     4 import java.util.regex.Pattern;
       
     5 import static hirondelle.web4j.util.Consts.SPACE;
       
     6 
       
     7 /**
       
     8  Convert a date-time from a string into a  {@link DateTime}.
       
     9  The primary use case for this class is converting date-times from a database <tt>ResultSet</tt>
       
    10  into a {@link DateTime}. It can also parse an ISO date-time containing a 'T' separator.
       
    11 */
       
    12 final class DateTimeParser  {
       
    13 
       
    14   /** 
       
    15    Thrown when the given string cannot be converted into a <tt>DateTime</tt>, since it doesn't 
       
    16    have a format allowed by this class. 
       
    17    An unchecked exception.
       
    18   */
       
    19   static final class UnknownDateTimeFormat extends RuntimeException {
       
    20     UnknownDateTimeFormat(String aMessage){   super(aMessage);   }
       
    21     UnknownDateTimeFormat(String aMessage, Throwable aEx){   super(aMessage, aEx);   }
       
    22   }
       
    23   
       
    24   DateTime parse(String aDateTime) {
       
    25     if(aDateTime == null){
       
    26       throw new NullPointerException("DateTime string is null");
       
    27     }
       
    28     String dateTime = aDateTime.trim();
       
    29     Parts parts = splitIntoDateAndTime(dateTime);
       
    30     if (parts.hasTwoParts()) {
       
    31       parseDate(parts.datePart);
       
    32       parseTime(parts.timePart);
       
    33     }
       
    34     else if (parts.hasDateOnly()){
       
    35       parseDate(parts.datePart);
       
    36     }
       
    37     else if (parts.hasTimeOnly()){
       
    38       parseTime(parts.timePart);
       
    39     }
       
    40     DateTime result = new DateTime(fYear, fMonth, fDay, fHour, fMinute, fSecond, fNanosecond);
       
    41     return result;
       
    42   }
       
    43   
       
    44   // PRIVATE
       
    45   
       
    46   /** 
       
    47    Gross pattern for dates. 
       
    48    Detailed validation is done by DateTime.
       
    49    The Group index VARIES for y-m-d according to which option is selected
       
    50    Year: Group 1, 4, 6
       
    51    Month: Group 2, 5
       
    52    Day: Group 3 
       
    53   */
       
    54   private static final Pattern DATE = Pattern.compile("(\\d{1,4})-(\\d\\d)-(\\d\\d)|(\\d{1,4})-(\\d\\d)|(\\d{1,4})");
       
    55   
       
    56   /** 
       
    57    Gross pattern for times. 
       
    58    Detailed validation is done by DateTime.   
       
    59    The Group index VARIES for h-m-s-f according to which option is selected
       
    60    Hour: Group 1, 5, 8, 10
       
    61    Minute: Group 2, 6, 9
       
    62    Second: Group 3, 7
       
    63    Microsecond:  Group 4
       
    64   */
       
    65   private static final String CL = "\\:"; //colon is a special character
       
    66   private static final String TT = "(\\d\\d)"; //colon is a special character
       
    67   private static final String NUM_DIGITS_FOR_FRACTIONAL_SECONDS = "9";
       
    68   private static final Integer NUM_DIGITS = Integer.valueOf(NUM_DIGITS_FOR_FRACTIONAL_SECONDS);
       
    69   private static final Pattern TIME = Pattern.compile("" +
       
    70       TT+CL+TT+CL+TT+ "\\." + "(\\d{1," + NUM_DIGITS_FOR_FRACTIONAL_SECONDS + "})" + "|" + 
       
    71       TT+CL+TT+CL+TT+ "|" + 
       
    72       TT+CL+TT+ "|" +
       
    73       TT
       
    74   );
       
    75   
       
    76   private static final String COLON = ":";
       
    77   private static final int THIRD_POSITION = 2;
       
    78   
       
    79   private Integer fYear;
       
    80   private Integer fMonth;
       
    81   private Integer fDay;
       
    82   private Integer fHour;
       
    83   private Integer fMinute;
       
    84   private Integer fSecond;
       
    85   private Integer fNanosecond;
       
    86   
       
    87   private class Parts {
       
    88     String datePart;
       
    89     String timePart;
       
    90     boolean hasTwoParts(){
       
    91       return datePart != null && timePart != null;
       
    92     }
       
    93     boolean hasDateOnly(){
       
    94       return timePart == null;
       
    95     }
       
    96     boolean hasTimeOnly(){
       
    97       return datePart == null;
       
    98     }
       
    99   }
       
   100   
       
   101   /** Date and time can be separated with a single space, or with a 'T' character (case-sensitive). */
       
   102   private Parts splitIntoDateAndTime(String aDateTime){
       
   103     Parts result = new Parts();
       
   104     int dateTimeSeparator = getDateTimeSeparator(aDateTime);
       
   105     boolean hasDateTimeSeparator = 0 < dateTimeSeparator  && dateTimeSeparator < aDateTime.length();
       
   106     if (hasDateTimeSeparator){
       
   107       result.datePart = aDateTime.substring(0, dateTimeSeparator);
       
   108       result.timePart = aDateTime.substring(dateTimeSeparator+1);
       
   109     }
       
   110     else if(hasColonInThirdPlace(aDateTime)){
       
   111       result.timePart = aDateTime;
       
   112     }
       
   113     else {
       
   114       result.datePart = aDateTime;
       
   115     }
       
   116     return result;
       
   117   }
       
   118   
       
   119   /** Return the index of a space character, or of a 'T' character. If not found, return -1.*/
       
   120   int getDateTimeSeparator(String aDateTime){
       
   121     int NOT_FOUND = -1;
       
   122     int result = NOT_FOUND;
       
   123     result = aDateTime.indexOf(SPACE);
       
   124     if(result == NOT_FOUND){
       
   125       result = aDateTime.indexOf("T");
       
   126     }
       
   127     return result;
       
   128   }
       
   129   
       
   130   private boolean hasColonInThirdPlace(String aDateTime){
       
   131     boolean result = false;
       
   132     if(aDateTime.length() >= THIRD_POSITION){
       
   133       result = COLON.equals(aDateTime.substring(THIRD_POSITION,THIRD_POSITION+1));
       
   134     }
       
   135     return result;
       
   136   }
       
   137   
       
   138   private void parseDate(String aDate) {
       
   139     Matcher matcher = DATE.matcher(aDate);
       
   140     if (matcher.matches()){
       
   141       String year = getGroup(matcher, 1, 4, 6);
       
   142       if(year !=null ){
       
   143         fYear = Integer.valueOf(year);
       
   144       }
       
   145       String month = getGroup(matcher, 2, 5);
       
   146       if(month !=null ){
       
   147         fMonth = Integer.valueOf(month);
       
   148       }
       
   149       String day = getGroup(matcher, 3);
       
   150       if(day !=null ){
       
   151         fDay = Integer.valueOf(day);
       
   152       }
       
   153     }
       
   154     else {
       
   155       throw new DateTimeParser.UnknownDateTimeFormat("Unexpected format for date:" + aDate);
       
   156     }
       
   157   }
       
   158 
       
   159   private String getGroup(Matcher aMatcher, int... aGroupIds){
       
   160     String result = null;
       
   161     for(int id: aGroupIds){
       
   162       result = aMatcher.group(id);
       
   163       if(result!=null) break;
       
   164     }
       
   165     return result;
       
   166   }
       
   167 
       
   168   private void parseTime(String aTime) {
       
   169     Matcher matcher = TIME.matcher(aTime);
       
   170     if (matcher.matches()){
       
   171       String hour = getGroup(matcher, 1, 5, 8, 10);
       
   172       if(hour !=null ){
       
   173         fHour = Integer.valueOf(hour);
       
   174       }
       
   175       String minute = getGroup(matcher, 2, 6, 9);
       
   176       if(minute !=null ){
       
   177         fMinute = Integer.valueOf(minute);
       
   178       }
       
   179       String second = getGroup(matcher, 3, 7);
       
   180       if(second !=null ){
       
   181         fSecond = Integer.valueOf(second);
       
   182       }
       
   183       String decimalSeconds = getGroup(matcher, 4);
       
   184       if(decimalSeconds !=null ){
       
   185         fNanosecond = Integer.valueOf(convertToNanoseconds(decimalSeconds));
       
   186       }
       
   187     }
       
   188     else {
       
   189       throw new DateTimeParser.UnknownDateTimeFormat("Unexpected format for time:" + aTime);
       
   190     }
       
   191   }
       
   192   
       
   193   /**
       
   194    Convert any number of decimals (1..9) into the form it would have taken if nanos had been used, 
       
   195    by adding any 0's to the right side.  
       
   196   */
       
   197   private String convertToNanoseconds(String aDecimalSeconds){
       
   198     StringBuilder result = new StringBuilder(aDecimalSeconds);
       
   199     while( result.length( ) < NUM_DIGITS ){
       
   200       result.append("0");
       
   201     }
       
   202     return result.toString();
       
   203   }
       
   204 }