简体   繁体   中英

Java String Parsing and Evaluating

All,

I am in the process or rewriting some code that I wrote a while back. The objective of the code was to calcualte a date and time based on a string in the following formats:

  • DayStart+2Hour+1Day-2Minutes
  • NOW+20Day
  • MonthStart+1Month

Which would take the start of the day (in local time), eg 2011-09-15 00:00:00 BST (2011-09-15 23:00 GMT) then add 2 hours, add 1 day, and subtract 2 minutes.

The implementation is in Java and the original algorithm was pretty basic. It iterated through each character in the string and appended to a buffer. The buffer was then checked to see if it ended with the strings I was looking (date specifier eg MINUTE, HOUR, DAYSTART, etc.) for then extracted the number and added to an ArrayList where DateOffset was a simple class with a int and String which was date specifier. Here is some sample code:

// hard coded for sample
String s = "DayStart+2Hour+1Day-2Minutes";

StringBuilder sbBuffer = new StringBuilder();
String buffer;

// iterate through date string
for (char c : s.toCharArray()) {
    sbBuffer.append(c);
    buffer = sbBuffer.toString();

    // check to see the end of the buffer string is what we expect
    if (buffer.endsWith("DAYSTART")) {
        offsets.add(new DateOffset(0, "DAYSTART"));
        sbBuffer = new StringBuilder();
    } else if (buffer.endsWith("DAY") && buffer.length() > 3) {
        String numberStringPart = buffer.substring(0, buffer.length() - 3);
        numberStringPart = numberStringPart.replaceAll("[+]", "").trim();  // need as parseInt does not like the +.

        offsets.add(new DateOffset(Integer.parseInt(numberStringPart), "DAY"));
        sbBuffer = new StringBuilder();
    } ... and so on ...
    else {
    }
}

After the string was parsed I iterated through ArrayList to calculate my datetime.

The problem with the above is probably not efficient although we have experienced no problems. It also does not pick up any errors so you could enter DayStart+2GKGKER.

I'm just trying to come up with some fresh and neat ideas on what to use to rewrite it. I have done a little regex but not too sure if this would be the best route.

Any thoughts?

Thanks,

Andez

Define a grammar for your expressions. Take a look at the ANTLR framework to help you construct a grammar and process your expressions.

If you're after rapid experimentation, sometimes a literate API combined with on the fly compilation is an easy way to go.

So, your example could look like (given appropriate static imports)

daystart().plus()
    .hours(2).plus()
    .days(1).minutes(2)

or even (given milliseconds as the basic units)

daystart() + hours(2) + days(1) - minutes(2)

Woohoo, that was fun! Thank you! :-)

public class DateExpressions {
    private Map<String, Date> dateVariables;
    private Map<String, Integer> temporalUnits;
    private Map<Character, Integer> temporalOperations;

    public static DateExpressions createInstance() {
        DateExpressions de = new DateExpressions();
        Calendar c = Calendar.getInstance();
        de.setVariable("NOW", c.getTime());

        c.set(Calendar.HOUR_OF_DAY, 0);
        c.set(Calendar.MINUTE, 0);
        c.set(Calendar.SECOND, 0);
        c.set(Calendar.MILLISECOND, 0);
        de.setVariable("DayStart", c.getTime());

        c.set(Calendar.DAY_OF_MONTH, 1);
        de.setVariable("MonthStart", c.getTime());

        return de;
    }

    public DateExpressions() {
        this.dateVariables = new HashMap<String, Date>();
        this.temporalUnits = new HashMap<String, Integer>();
        this.temporalUnits.put("Second", Calendar.SECOND);
        this.temporalUnits.put("Minute", Calendar.MINUTE);
        this.temporalUnits.put("Hour", Calendar.HOUR_OF_DAY);
        this.temporalUnits.put("Day", Calendar.DATE);
        this.temporalUnits.put("Month", Calendar.MONTH);
        this.temporalUnits.put("Year", Calendar.YEAR);

        this.temporalOperations = new HashMap<Character, Integer>();
        this.temporalOperations.put('+', 1);
        this.temporalOperations.put('-', -1);
    }

    public void setVariable(String key, Date value) {
        this.dateVariables.put(key, value);
    }

    public Date parseExpression(String expr) throws IOException {
        StringReader sr = new StringReader(expr);
        String s;
        int n;
        char c;

        int offset;
        int unit;
        int op = 1;

        Calendar base = null;
        StringBuilder sb1 = new StringBuilder();
        StringBuilder sb2 = new StringBuilder();
        while ((n = sr.read()) != -1) {
            c = (char) n;

            if (base == null && temporalOperations.containsKey(c)) {
                s = sb2.toString();
                if (!dateVariables.containsKey(s)) {
                    throw new IOException("Unknown variable '" + s + "' used");
                }

                base = Calendar.getInstance();
                base.setTime(dateVariables.get(sb2.toString()));
                op = temporalOperations.get(c);

                sb1.setLength(0);
                sb2.setLength(0);
            } else if (temporalOperations.containsKey(c)) {
                if (!temporalUnits.containsKey(sb2.toString())) {
                    throw new IOException(
                            "Parse error: unknown temporal unit used '"
                                    + sb2.toString() + "'");
                }

                offset = Integer.parseInt(sb1.toString());
                unit = temporalUnits.get(sb2.toString());

                base.add(unit, op * offset);

                op = temporalOperations.get(c);
                sb1.setLength(0);
                sb2.setLength(0);
            } else if (Character.isDigit(c)) {
                sb1.append(c);
            } else {
                sb2.append(c);
            }
        }

        if (!temporalUnits.containsKey(sb2.toString())) {
            throw new IOException("Parse error: unknown temporal unit used '"
                    + sb2.toString() + "'");
        }

        offset = Integer.parseInt(sb1.toString());
        unit = temporalUnits.get(sb2.toString());

        base.add(unit, op * offset);

        return base.getTime();
    }

    public static void main(String[] args) throws IOException {
        DateExpressions de = DateExpressions.createInstance();
        System.out.println(de.parseExpression("DayStart+2Hour+1Day-2Minute"));
        System.out.println(de.parseExpression("NOW+20Day"));
        System.out.println(de.parseExpression("MonthStart+1Month"));
    }
}

Regex seems to be the best bet for such a scenario. Although, I'm puzzled why would you want to interpret strings in this manner, rather than having sophisticated APIs.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM