make RobustDateParser more robust
[org.ibex.util.git] / src / org / ibex / util / RobustDateParser.java
1 package org.ibex.util;
2 import java.util.*;
3 import java.io.*;
4
5 // FEATURE: cope with stuff like "next tuesday" or "in three hours"
6
7 /** prototype parser for free-form dates; mostly needs to handle RFC2822 Date field */
8 public class RobustDateParser {
9
10     private static final int CHARCLASS_OTHER = 0;
11     private static final int CHARCLASS_WS    = 1;
12     private static final int CHARCLASS_ALPHA = 2;
13     private static final int CHARCLASS_NUM   = 3;
14     private static final int CHARCLASS_SYM   = 4;
15
16     public static int charClass(char c) {
17         if (Character.isWhitespace(c)) return CHARCLASS_WS;
18         if (Character.isLetter(c)) return CHARCLASS_ALPHA;
19         if (Character.isDigit(c)) return CHARCLASS_NUM;
20         return CHARCLASS_SYM;
21     }
22
23     public static int parseMonth(String s) {
24         s = s.trim();
25         if (s.length() < 3) return -1;
26         s = s.substring(0, 3).toLowerCase();
27         if (s.equals("jan")) return 1;
28         if (s.equals("feb")) return 2;
29         if (s.equals("mar")) return 3;
30         if (s.equals("apr")) return 4;
31         if (s.equals("may")) return 5;
32         if (s.equals("jun")) return 6;
33         if (s.equals("jul")) return 7;
34         if (s.equals("aug")) return 8;
35         if (s.equals("sep")) return 9;
36         if (s.equals("oct")) return 10;
37         if (s.equals("nov")) return 11;
38         if (s.equals("dec")) return 12;
39         return -1;
40     }
41
42     public static String[] tokenize(String s) {
43         Vec components = new Vec();
44         String tok = "";
45         for(int i=0; i<s.length(); i++) {
46             char c = s.charAt(i);
47             if (tok.length() > 0 && charClass(c) != charClass(tok.charAt(tok.length()-1))) {
48                 components.add(tok);
49                 tok = "";
50                 i--;
51                 continue;
52             }
53             if (charClass(c) == CHARCLASS_WS) continue;
54             tok += c;
55         }
56         if (tok.length() > 0)
57             components.add(tok);
58         String[] ret = new String[components.size()];
59         components.toArray(ret);
60         return ret;
61     }
62
63     public static Date parseDate(String s) {
64         if (s==null) return null;
65         try {
66             return parseDate_(s);
67         } catch (Exception e) {
68             Log.error(RobustDateParser.class, e);
69             return null;
70         }
71     }
72     public static Date parseDate_(String s) {
73         String[] toks = tokenize(s);
74         int[] cclass = new int[toks.length];
75         for(int i=0; i<toks.length; i++)
76             cclass[i] = charClass(toks[i].charAt(0));
77         int day   = -1;
78         int month = -1;
79         int year  = -1;
80         int hour  = -1;
81         int min   = -1;
82         int sec   = -1;
83         TimeZone tz = null;
84
85         for(int i=0; i<toks.length; i++) {
86             if (cclass[i]==CHARCLASS_ALPHA) {
87                 int tempmonth = parseMonth(toks[i]);
88                 if (tempmonth != -1) {
89                     month = tempmonth;
90                     if (i<toks.length-1 && cclass[i+1]==CHARCLASS_NUM && toks[i+1].length()<=2)
91                         day = Integer.parseInt(toks[i+1]);
92                     else if (i>0 && cclass[i-1]==CHARCLASS_NUM && toks[i-1].length()<=2)
93                         day = Integer.parseInt(toks[i-1]);
94                 } else if (tz==null && toks[i].length()==3 && toks[i].toUpperCase().equals(toks[i])) {
95                     tz = TimeZone.getTimeZone(toks[i]);
96                 }
97             }
98             if (cclass[i]==CHARCLASS_NUM) {
99                 if (toks[i].length()==4) {
100                     int y = Integer.parseInt(toks[i]);
101                     if (y > 1960 && y < 2100)
102                         year = Integer.parseInt(toks[i]);
103                     else if (i>0 && (toks[i-1].equals("+") || toks[i-1].equals("-"))) {
104                         String st = (toks[i-1]+toks[i]).trim();
105                         while (st.length() > 0 && st.charAt(0)=='+') st = st.substring(1);
106                         int ofs = Integer.parseInt(st);
107                         if (ofs % 100 == 0)
108                             tz = new SimpleTimeZone((ofs / 100) * 60 * 60 * 1000, (toks[i-1]+toks[i]));
109                     }
110                 } else if (i<toks.length-1 && toks[i+1].equals(":")) {
111                     if      (hour == -1) hour = Integer.parseInt(toks[i]);
112                     else if (min == -1)  min  = Integer.parseInt(toks[i]);
113                 } else if (i>0 && toks[i-1].equals(":")) {
114                     if (sec == -1)  sec  = Integer.parseInt(toks[i]);
115                 }
116             }
117         }
118
119         GregorianCalendar gc = new GregorianCalendar();
120         if (tz != null) gc.setTimeZone(tz);
121         gc.set(year, month-1, day, hour, min, sec); 
122         Date d = gc.getTime();
123
124         /*
125         StringBuffer sb = new StringBuffer();
126         sb.append("components: ");
127         for(String t : toks)
128             sb.append("\""+t+"\" ");
129         Log.warn("RobustDateParser",
130                  sb.toString() + "\n" +
131                  "components: " + year+":"+month+":"+day+":"+hour+":"+min+":"+sec + "\n" +
132                  "parsed as: " + d);
133         */
134         return d;
135     }
136
137 }