add RobustDateParser
[org.ibex.util.git] / src / org / ibex / util / RobustDateParser.java
diff --git a/src/org/ibex/util/RobustDateParser.java b/src/org/ibex/util/RobustDateParser.java
new file mode 100644 (file)
index 0000000..3340dd8
--- /dev/null
@@ -0,0 +1,135 @@
+package org.ibex.util;
+import java.util.*;
+import java.io.*;
+
+// FEATURE: cope with stuff like "next tuesday" or "in three hours"
+
+/** prototype parser for free-form dates; mostly needs to handle RFC2822 Date field */
+public class RobustDateParser {
+
+    private static final int CHARCLASS_OTHER = 0;
+    private static final int CHARCLASS_WS    = 1;
+    private static final int CHARCLASS_ALPHA = 2;
+    private static final int CHARCLASS_NUM   = 3;
+    private static final int CHARCLASS_SYM   = 4;
+
+    public static int charClass(char c) {
+        if (Character.isWhitespace(c)) return CHARCLASS_WS;
+        if (Character.isLetter(c)) return CHARCLASS_ALPHA;
+        if (Character.isDigit(c)) return CHARCLASS_NUM;
+        return CHARCLASS_SYM;
+    }
+
+    public static int parseMonth(String s) {
+        s = s.trim().substring(0, 3).toLowerCase();
+        if (s.equals("jan")) return 1;
+        if (s.equals("feb")) return 2;
+        if (s.equals("mar")) return 3;
+        if (s.equals("apr")) return 4;
+        if (s.equals("may")) return 5;
+        if (s.equals("jun")) return 6;
+        if (s.equals("jul")) return 7;
+        if (s.equals("aug")) return 8;
+        if (s.equals("sep")) return 9;
+        if (s.equals("oct")) return 10;
+        if (s.equals("nov")) return 11;
+        if (s.equals("dec")) return 12;
+        return -1;
+    }
+
+    public static String[] tokenize(String s) {
+        Vec components = new Vec();
+        String tok = "";
+        for(int i=0; i<s.length(); i++) {
+            char c = s.charAt(i);
+            if (tok.length() > 0 && charClass(c) != charClass(tok.charAt(tok.length()-1))) {
+                components.add(tok);
+                tok = "";
+                i--;
+                continue;
+            }
+            if (charClass(c) == CHARCLASS_WS) continue;
+            tok += c;
+        }
+        if (tok.length() > 0)
+            components.add(tok);
+        String[] ret = new String[components.size()];
+        components.toArray(ret);
+        return ret;
+    }
+
+    public static Date parseDate(String s) {
+        if (s==null) return null;
+        try {
+            return parseDate_(s);
+        } catch (Exception e) {
+            Log.error(RobustDateParser.class, e);
+            return null;
+        }
+    }
+    public static Date parseDate_(String s) {
+        String[] toks = tokenize(s);
+        int[] cclass = new int[toks.length];
+        for(int i=0; i<toks.length; i++)
+            cclass[i] = charClass(toks[i].charAt(0));
+        int day   = -1;
+        int month = -1;
+        int year  = -1;
+        int hour  = -1;
+        int min   = -1;
+        int sec   = -1;
+        TimeZone tz = null;
+
+        for(int i=0; i<toks.length; i++) {
+            if (cclass[i]==CHARCLASS_ALPHA) {
+                int tempmonth = parseMonth(toks[i]);
+                if (tempmonth != -1) {
+                    month = tempmonth;
+                    if (i<toks.length-1 && cclass[i+1]==CHARCLASS_NUM && toks[i+1].length()<=2)
+                        day = Integer.parseInt(toks[i+1]);
+                    else if (i>0 && cclass[i-1]==CHARCLASS_NUM && toks[i-1].length()<=2)
+                        day = Integer.parseInt(toks[i-1]);
+                } else if (tz==null && toks[i].length()==3 && toks[i].toUpperCase().equals(toks[i])) {
+                    tz = TimeZone.getTimeZone(toks[i]);
+                }
+            }
+            if (cclass[i]==CHARCLASS_NUM) {
+                if (toks[i].length()==4) {
+                    int y = Integer.parseInt(toks[i]);
+                    if (y > 1960 && y < 2100)
+                        year = Integer.parseInt(toks[i]);
+                    else if (i>0 && (toks[i-1].equals("+") || toks[i-1].equals("-"))) {
+                        String st = (toks[i-1]+toks[i]).trim();
+                        while (st.length() > 0 && st.charAt(0)=='+') st = st.substring(1);
+                        int ofs = Integer.parseInt(st);
+                        if (ofs % 100 == 0)
+                            tz = new SimpleTimeZone((ofs / 100) * 60 * 60 * 1000, (toks[i-1]+toks[i]));
+                    }
+                } else if (i<toks.length-1 && toks[i+1].equals(":")) {
+                    if      (hour == -1) hour = Integer.parseInt(toks[i]);
+                    else if (min == -1)  min  = Integer.parseInt(toks[i]);
+                } else if (i>0 && toks[i-1].equals(":")) {
+                    if (sec == -1)  sec  = Integer.parseInt(toks[i]);
+                }
+            }
+        }
+
+        GregorianCalendar gc = new GregorianCalendar();
+        if (tz != null) gc.setTimeZone(tz);
+        gc.set(year, month-1, day, hour, min, sec); 
+        Date d = gc.getTime();
+
+        /*
+        StringBuffer sb = new StringBuffer();
+        sb.append("components: ");
+        for(String t : toks)
+            sb.append("\""+t+"\" ");
+        Log.warn("RobustDateParser",
+                 sb.toString() + "\n" +
+                 "components: " + year+":"+month+":"+day+":"+hour+":"+min+":"+sec + "\n" +
+                 "parsed as: " + d);
+        */
+        return d;
+    }
+
+}