add -shell-date flag to specify time for $(shell date)

--shell-date=ref uses go reference time. useful to check
parsed data with golden data.
diff --git a/expr.go b/expr.go
index ad05c4b..91c861b 100644
--- a/expr.go
+++ b/expr.go
@@ -19,6 +19,7 @@
 	"errors"
 	"fmt"
 	"io"
+	"regexp"
 	"strconv"
 	"strings"
 	"sync"
@@ -623,6 +624,21 @@
 func (m matchVarref) Serialize() SerializableVar      { panic("not implemented") }
 func (m matchVarref) Dump(w io.Writer)                { panic("not implemented") }
 
+type literalRE struct {
+	*regexp.Regexp
+}
+
+func mustLiteralRE(s string) literalRE {
+	return literalRE{
+		Regexp: regexp.MustCompile(s),
+	}
+}
+
+func (r literalRE) String() string                  { return r.Regexp.String() }
+func (r literalRE) Eval(w io.Writer, ev *Evaluator) { panic("not implemented") }
+func (r literalRE) Serialize() SerializableVar      { panic("not implemented") }
+func (r literalRE) Dump(w io.Writer)                { panic("not implemented") }
+
 func matchValue(expr, pat Value) bool {
 	switch pat := pat.(type) {
 	case literal:
@@ -647,6 +663,17 @@
 			}
 			return nil, false
 		}
+		if patre, ok := pat[i].(literalRE); ok {
+			re := patre.Regexp
+			m := re.FindStringSubmatch(expr[i].String())
+			if m == nil {
+				return nil, false
+			}
+			for _, sm := range m[1:] {
+				matches = append(matches, literal(sm))
+			}
+			continue
+		}
 		if !matchValue(expr[i], pat[i]) {
 			return nil, false
 		}
diff --git a/func.go b/func.go
index a164309..364545b 100644
--- a/func.go
+++ b/func.go
@@ -774,6 +774,9 @@
 	shellVar := ev.LookupVar("SHELL")
 	// TODO: Should be Eval, not String.
 	cmdline := []string{shellVar.String(), "-c", arg}
+	if katiLogFlag {
+		Logf("shell %q", cmdline)
+	}
 	cmd := exec.Cmd{
 		Path:   cmdline[0],
 		Args:   cmdline,
@@ -793,13 +796,16 @@
 	if len(f.args)-1 < 1 {
 		return f
 	}
-	if !useFindCache {
+	if !useFindCache && !useShellBuiltins {
 		return f
 	}
 
-	expr, ok := f.args[1].(Expr)
-	if !ok {
-		return f
+	var expr Expr
+	switch v := f.args[1].(type) {
+	case Expr:
+		expr = v
+	default:
+		expr = Expr{v}
 	}
 	if useShellBuiltins {
 		// hack for android
diff --git a/main.go b/main.go
index 691c332..15ad142 100644
--- a/main.go
+++ b/main.go
@@ -29,6 +29,8 @@
 	"time"
 )
 
+const shellDateTimeformat = time.RFC3339
+
 var (
 	katiLogFlag           bool
 	makefileFlag          string
@@ -55,6 +57,7 @@
 	findCacheLeafNames    string
 	useWildcardCache      bool
 	useShellBuiltins      bool
+	shellDate             string
 	generateNinja         bool
 	ignoreOptionalInclude string
 	gomaDir               string
@@ -104,6 +107,7 @@
 		"space separated leaf names for find cache.")
 	flag.BoolVar(&useWildcardCache, "use_wildcard_cache", true, "Use wildcard cache.")
 	flag.BoolVar(&useShellBuiltins, "use_shell_builtins", true, "Use shell builtins")
+	flag.StringVar(&shellDate, "shell_date", "", "specify $(shell date) time as "+shellDateTimeformat)
 	flag.BoolVar(&generateNinja, "ninja", false, "Generate build.ninja.")
 	flag.StringVar(&ignoreOptionalInclude, "ignore_optional_include", "", "If specified, skip reading -include directives start with the specified path.")
 	flag.StringVar(&gomaDir, "goma_dir", "", "If specified, use goma to build C/C++ files.")
@@ -324,6 +328,17 @@
 		defer traceEvent.stop()
 	}
 
+	if shellDate != "" {
+		if shellDate == "ref" {
+			shellDate = shellDateTimeformat[:20] // until Z, drop 07:00
+		}
+		t, err := time.Parse(shellDateTimeformat, shellDate)
+		if err != nil {
+			panic(err)
+		}
+		shellDateTimestamp = t
+	}
+
 	if findCacheLeafNames != "" {
 		androidDefaultLeafNames = strings.Fields(findCacheLeafNames)
 	}
diff --git a/shellutil.go b/shellutil.go
index cf6d514..c73711d 100644
--- a/shellutil.go
+++ b/shellutil.go
@@ -15,8 +15,10 @@
 package main
 
 import (
+	"fmt"
 	"io"
 	"strings"
+	"time"
 )
 
 var androidDefaultLeafNames = []string{"CleanSpec.mk", "Android.mk"}
@@ -217,6 +219,20 @@
 			}
 		},
 	},
+	{
+		name: "shell-date",
+		pattern: Expr{
+			mustLiteralRE(`date \+(\S+)`),
+		},
+		compact: compactShellDate,
+	},
+	{
+		name: "shell-date-quoted",
+		pattern: Expr{
+			mustLiteralRE(`date "\+([^"]+)"`),
+		},
+		compact: compactShellDate,
+	},
 }
 
 type funcShellAndroidRot13 struct {
@@ -390,3 +406,44 @@
 		androidFindCache.findleaves(&sw, dir, name, prunes, f.mindepth)
 	}
 }
+
+var (
+	shellDateTimestamp time.Time
+	shellDateFormatRef = map[string]string{
+		"%Y": "2006",
+		"%m": "01",
+		"%d": "02",
+		"%H": "15",
+		"%M": "04",
+		"%S": "05",
+		"%b": "Jan",
+		"%k": "15", // XXX
+	}
+)
+
+type funcShellDate struct {
+	*funcShell
+	format string
+}
+
+func compactShellDate(sh *funcShell, v []Value) Value {
+	if shellDateTimestamp.IsZero() {
+		return sh
+	}
+	tf, ok := v[0].(literal)
+	if !ok {
+		return sh
+	}
+	tfstr := string(tf)
+	for k, v := range shellDateFormatRef {
+		tfstr = strings.Replace(tfstr, k, v, -1)
+	}
+	return &funcShellDate{
+		funcShell: sh,
+		format:    tfstr,
+	}
+}
+
+func (f *funcShellDate) Eval(w io.Writer, ev *Evaluator) {
+	fmt.Fprint(w, shellDateTimestamp.Format(f.format))
+}
diff --git a/shellutil_test.go b/shellutil_test.go
index e438b88..c54e761 100644
--- a/shellutil_test.go
+++ b/shellutil_test.go
@@ -14,7 +14,10 @@
 
 package main
 
-import "testing"
+import (
+	"testing"
+	"time"
+)
 
 func TestRot13(t *testing.T) {
 	for _, tc := range []struct {
@@ -37,3 +40,63 @@
 		}
 	}
 }
+
+func TestShellDate(t *testing.T) {
+	ts := shellDateTimestamp
+	shellDateTimestamp = time.Now()
+	defer func() {
+		shellDateTimestamp = ts
+	}()
+	for _, tc := range []struct {
+		sharg  literal
+		format string
+	}{
+		{
+			sharg:  literal("date +%Y-%m-%d"),
+			format: "2006-01-02",
+		},
+		{
+			sharg:  literal("date +%Y%m%d.%H%M%S"),
+			format: "20060102.150405",
+		},
+		{
+			sharg:  literal(`date "+%d %b %Y %k:%M"`),
+			format: "02 Jan 2006 15:04",
+		},
+	} {
+		var matched bool
+		for _, b := range shBuiltins {
+			if b.name != "shell-date" && b.name != "shell-date-quoted" {
+				continue
+			}
+			m, ok := matchExpr(Expr{tc.sharg}, b.pattern)
+			if !ok {
+				t.Logf("%s not match with %s", b.name, tc.sharg)
+				continue
+			}
+			f := &funcShell{
+				fclosure: fclosure{
+					args: []Value{
+						literal("(shell"),
+						tc.sharg,
+					},
+				},
+			}
+			v := b.compact(f, m)
+			sd, ok := v.(*funcShellDate)
+			if !ok {
+				t.Errorf("%s: matched %s but not compacted", tc.sharg, b.name)
+				continue
+			}
+			if got, want := sd.format, tc.format; got != want {
+				t.Errorf("%s: format=%q, want=%q - %s", tc.sharg, got, want, b.name)
+				continue
+			}
+			matched = true
+			break
+		}
+		if !matched {
+			t.Errorf("%s: not matched", tc.sharg)
+		}
+	}
+}