Skip to content

Commit

Permalink
Implemented date/time parsing and tests.
Browse files Browse the repository at this point in the history
  • Loading branch information
Knetic committed Dec 28, 2014
1 parent ed910d3 commit 500511d
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 3 deletions.
2 changes: 2 additions & 0 deletions TokenKind.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const (
NUMERIC
BOOLEAN
STRING
TIME
VARIABLE

COMPARATOR
Expand All @@ -35,6 +36,7 @@ func GetTokenKindString(kind TokenKind) string {
case NUMERIC : return "NUMERIC";
case BOOLEAN : return "BOOLEAN";
case STRING : return "STRING";
case TIME : return "TIME";
case VARIABLE : return "VARIABLE";
case COMPARATOR : return "COMPARATOR";
case LOGICALOP : return "LOGICALOP";
Expand Down
16 changes: 16 additions & 0 deletions lexerState.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ var validLexerStates = []lexerState {
BOOLEAN,
VARIABLE,
STRING,
TIME,
CLAUSE,
CLAUSE_CLOSE,
},
Expand All @@ -36,6 +37,7 @@ var validLexerStates = []lexerState {
BOOLEAN,
VARIABLE,
STRING,
TIME,
CLAUSE,
CLAUSE_CLOSE,
},
Expand Down Expand Up @@ -77,6 +79,18 @@ var validLexerStates = []lexerState {
CLAUSE_CLOSE,
},
},
lexerState {

kind: TIME,
isEOF: true,
validNextKinds: []TokenKind {

MODIFIER,
COMPARATOR,
LOGICALOP,
CLAUSE_CLOSE,
},
},
lexerState {

kind: VARIABLE,
Expand Down Expand Up @@ -111,6 +125,7 @@ var validLexerStates = []lexerState {
BOOLEAN,
VARIABLE,
STRING,
TIME,
CLAUSE,
CLAUSE_CLOSE,
},
Expand All @@ -125,6 +140,7 @@ var validLexerStates = []lexerState {
BOOLEAN,
VARIABLE,
STRING,
TIME,
CLAUSE,
CLAUSE_CLOSE,
},
Expand Down
65 changes: 63 additions & 2 deletions parsing.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package govaluate
import (
"fmt"
"errors"
"time"
"bytes"
"strconv"
"unicode"
Expand Down Expand Up @@ -66,6 +67,7 @@ func readToken(stream *lexerStream) (ExpressionToken, error, bool) {

var ret ExpressionToken
var tokenValue interface{}
var tokenTime time.Time;
var tokenString string
var kind TokenKind
var character rune
Expand Down Expand Up @@ -118,10 +120,18 @@ func readToken(stream *lexerStream) (ExpressionToken, error, bool) {

if(!isNotQuote(character)) {
tokenValue = readUntilFalse(stream, true, false, isNotQuote);
kind = STRING;


// advance the stream one position, since reading until false assumes the terminator is a real token
stream.rewind(-1)

// check to see if this can be parsed as a time.
tokenTime, found = tryParseTime(tokenValue.(string));
if(found) {
kind = TIME;
tokenValue = tokenTime;
} else {
kind = STRING;
}
break;
}

Expand Down Expand Up @@ -224,3 +234,54 @@ func isNotAlphanumeric(character rune) bool {
character == ')' ||
!isNotQuote(character));
}

/*
Attempts to parse the [candidate] as a Time.
Tries a series of standardized date formats, returns the Time if one applies,
otherwise returns false through the second return.
*/
func tryParseTime(candidate string) (time.Time, bool) {

var ret time.Time;
var found bool;

timeFormats := [...]string {
time.ANSIC,
time.UnixDate,
time.RubyDate,
time.Kitchen,
time.RFC3339,
time.RFC3339Nano,
"2006-01-02", // RFC 3339
"2006-01-02 15:04", // RFC 3339 with minutes
"2006-01-02 15:04:05", // RFC 3339 with seconds
"2006-01-02 15:04:05-07:00", // RFC 3339 with seconds and timezone
"2006-01-02T15Z0700", // ISO8601 with hour
"2006-01-02T15:04Z0700", // ISO8601 with minutes
"2006-01-02T15:04:05Z0700", // ISO8601 with seconds
"2006-01-02T15:04:05.999999999Z0700", // ISO8601 with nanoseconds
};

for _, format := range timeFormats {

ret, found = tryParseExactTime(candidate, format);
if(found) {
return ret, true;
}
}

return time.Now(), false;
}

func tryParseExactTime(candidate string, format string) (time.Time, bool) {

var ret time.Time;
var err error;

ret, err = time.Parse(format, candidate);
if(err != nil) {
return time.Now(), false;
}

return ret, true;
}
40 changes: 39 additions & 1 deletion parsing_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package govaluate

import (
"strings"
"time"
"testing"
)

Expand Down Expand Up @@ -52,6 +53,39 @@ func TestConstantParsing(test *testing.T) {
},
},
},
TokenParsingTest {

Name: "Single time, RFC3339, only date",
Input: "'2014-01-02'",
Expected: []ExpressionToken {
ExpressionToken {
Kind: TIME,
Value: time.Date(2014, time.January, 2, 0, 0, 0, 0, time.UTC),
},
},
},
TokenParsingTest {

Name: "Single time, RFC3339, with hh:mm",
Input: "'2014-01-02 14:12'",
Expected: []ExpressionToken {
ExpressionToken {
Kind: TIME,
Value: time.Date(2014, time.January, 2, 14, 12, 0, 0, time.UTC),
},
},
},
TokenParsingTest {

Name: "Single time, RFC3339, with hh:mm:ss",
Input: "'2014-01-02 14:12:22'",
Expected: []ExpressionToken {
ExpressionToken {
Kind: TIME,
Value: time.Date(2014, time.January, 2, 14, 12, 22, 0, time.UTC),
},
},
},
TokenParsingTest {

Name: "Single boolean",
Expand Down Expand Up @@ -445,6 +479,7 @@ func runTokenParsingTest(tokenParsingTests []TokenParsingTest, test *testing.T)
var expression *EvaluableExpression
var actualTokens []ExpressionToken;
var actualToken ExpressionToken
var expectedTokenKindString, actualTokenKindString string;
var expectedTokenLength, actualTokenLength int
var err error

Expand Down Expand Up @@ -479,8 +514,11 @@ func runTokenParsingTest(tokenParsingTests []TokenParsingTest, test *testing.T)
actualToken = actualTokens[i]
if(actualToken.Kind != expectedToken.Kind) {

actualTokenKindString = GetTokenKindString(actualToken.Kind);
expectedTokenKindString = GetTokenKindString(expectedToken.Kind);

test.Logf("Test '%s' failed:", parsingTest.Name)
test.Logf("Expected token kind '%v' does not match '%v'", expectedToken.Kind, actualToken.Kind)
test.Logf("Expected token kind '%v' does not match '%v'", expectedTokenKindString, actualTokenKindString)
test.Fail()
continue
}
Expand Down

0 comments on commit 500511d

Please sign in to comment.