Procházet zdrojové kódy

Add support for alternative timestamp formats

yaml.org describes several alternative formats for timestamps not
covered by the default implementation of `time.UnmarshalText()`. These
are handled explicitly in this change in resolve.go

Timestamps will only be collected if there is an explicit timestamp
tag (ie, not `!!str`) or if implicit type detection is enabled.
abishopric před 10 roky
rodič
revize
c75e52ecee
3 změnil soubory, kde provedl 60 přidání a 3 odebrání
  1. 5 0
      decode.go
  2. 12 0
      decode_test.go
  3. 43 3
      resolve.go

+ 5 - 0
decode.go

@@ -453,6 +453,11 @@ func (d *decoder) scalar(n *node, out reflect.Value) (good bool) {
 			out.SetFloat(resolved)
 			good = true
 		}
+	case reflect.Struct:
+		if out.Type() == reflect.TypeOf(resolved) {
+			out.Set(reflect.ValueOf(resolved))
+			good = true
+		}
 	case reflect.Ptr:
 		if out.Type().Elem() == reflect.TypeOf(resolved) {
 			// TODO DOes this make sense? When is out a Ptr except when decoding a nil value?

+ 12 - 0
decode_test.go

@@ -559,6 +559,18 @@ var unmarshalTests = []struct {
 		"a: 2015-02-24T18:19:39Z\n",
 		map[string]time.Time{"a": time.Unix(1424801979, 0).In(time.UTC)},
 	},
+	{
+		"a: 2015-01-01",
+		map[string]time.Time{"a": time.Unix(1420070400, 0)},
+	},
+	{
+		"a: !!str 2015-01-01",
+		map[string]string{"a": "2015-01-01"},
+	},
+	{
+		"a: \"2015-01-01\"",
+		map[string]interface{}{"a": "2015-01-01"},
+	},
 
 	// Encode empty lists as zero-length slices.
 	{

+ 43 - 3
resolve.go

@@ -6,6 +6,7 @@ import (
 	"regexp"
 	"strconv"
 	"strings"
+	"time"
 	"unicode/utf8"
 )
 
@@ -75,7 +76,7 @@ func longTag(tag string) string {
 
 func resolvableTag(tag string) bool {
 	switch tag {
-	case "", yaml_STR_TAG, yaml_BOOL_TAG, yaml_INT_TAG, yaml_FLOAT_TAG, yaml_NULL_TAG:
+	case "", yaml_STR_TAG, yaml_BOOL_TAG, yaml_INT_TAG, yaml_FLOAT_TAG, yaml_NULL_TAG, yaml_TIMESTAMP_TAG:
 		return true
 	}
 	return false
@@ -125,6 +126,37 @@ func resolve(tag string, in string) (rtag string, out interface{}) {
 
 		case 'D', 'S':
 			// Int, float, or timestamp.
+
+			// Handle custom timestamp formats as described on http://yaml.org/type/timestamp.html
+			// RFC3339 is handled automatically by the time.Time implementation of the
+			// encoding.TextUnmarshaler interface but we are going to explicitly
+			// handle it here. We should only perform timestamp manipulation if
+			// there is either no quotes on the value or there is an explicit !!timestamp tag.
+
+			if shortTag(tag) == shortTag(yaml_TIMESTAMP_TAG) || tag == "" {
+				var possibleTime time.Time
+				if tryTime(time.RFC3339, in, &possibleTime) {
+					return yaml_TIMESTAMP_TAG, possibleTime
+				}
+
+				// valid iso8601
+				if tryTime("2006-01-02t15:04:05.99-07:00", in, &possibleTime) {
+					return yaml_TIMESTAMP_TAG, possibleTime
+				}
+				// space separated
+				if tryTime("2006-01-02 15:04:05.99 -7", in, &possibleTime) {
+					return yaml_TIMESTAMP_TAG, possibleTime
+				}
+				// no time zone
+				if tryTime("2006-01-02 15:04:05.99", in, &possibleTime) {
+					return yaml_TIMESTAMP_TAG, possibleTime
+				}
+				// date (00:00:00Z)
+				if tryTime("2006-01-02", in, &possibleTime) {
+					return yaml_TIMESTAMP_TAG, possibleTime
+				}
+			}
+
 			plain := strings.Replace(in, "_", "", -1)
 			intv, err := strconv.ParseInt(plain, 0, 64)
 			if err == nil {
@@ -167,8 +199,6 @@ func resolve(tag string, in string) (rtag string, out interface{}) {
 					}
 				}
 			}
-			// XXX Handle timestamps here.
-
 		default:
 			panic("resolveTable item not yet handled: " + string(rune(hint)) + " (with " + in + ")")
 		}
@@ -182,6 +212,16 @@ func resolve(tag string, in string) (rtag string, out interface{}) {
 	return yaml_BINARY_TAG, encodeBase64(in)
 }
 
+func tryTime(format, value string, t *time.Time) bool {
+	attempt, err := time.Parse(format, value)
+	if err == nil {
+		*t = attempt
+		return true
+	} else {
+		return false
+	}
+}
+
 // encodeBase64 encodes s as base64 that is broken up into multiple lines
 // as appropriate for the resulting length.
 func encodeBase64(s string) string {