Skip to content

Commit

Permalink
Fix timestamp resolution in yaml
Browse files Browse the repository at this point in the history
  • Loading branch information
TomWright committed Aug 29, 2023
1 parent bccbedf commit 20b46bc
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 8 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed

- Resolved an issue with YAML parser that was causing strings to be read as numbers.
- Timestamps can now be resolved as expected in YAML.

## [v2.3.4] - 2023-06-01

Expand Down
17 changes: 9 additions & 8 deletions dencoding/yaml.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
package dencoding

const (
yamlTagString = "!!str"
yamlTagMap = "!!map"
yamlTagArray = "!!seq"
yamlTagNull = "!!null"
yamlTagBinary = "!!binary"
yamlTagBool = "!!bool"
yamlTagInt = "!!int"
yamlTagFloat = "!!float"
yamlTagString = "!!str"
yamlTagMap = "!!map"
yamlTagArray = "!!seq"
yamlTagNull = "!!null"
yamlTagBinary = "!!binary"
yamlTagBool = "!!bool"
yamlTagInt = "!!int"
yamlTagFloat = "!!float"
yamlTagTimestamp = "!!timestamp"
)

// YAMLEncoderOption is identifies an option that can be applied to a YAML encoder.
Expand Down
44 changes: 44 additions & 0 deletions dencoding/yaml_decoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"io"
"reflect"
"strconv"
"time"
)

// YAMLDecoder wraps a standard yaml encoder to implement custom ordering logic.
Expand Down Expand Up @@ -116,6 +117,12 @@ func (decoder *YAMLDecoder) getScalarNodeValue(node *yaml.Node) (any, error) {
return strconv.ParseInt(node.Value, 0, 64)
case yamlTagString:
return node.Value, nil
case yamlTagTimestamp:
value, ok := parseTimestamp(node.Value)
if !ok {
return value, fmt.Errorf("could not parse timestamp: %v", node.Value)
}
return value, nil
default:
return nil, fmt.Errorf("unhandled scalar node tag: %v", node.ShortTag())
}
Expand All @@ -128,3 +135,40 @@ func (decoder *YAMLDecoder) nextNode() (*yaml.Node, error) {
}
return &node, nil
}

// This is a subset of the formats allowed by the regular expression
// defined at http://yaml.org/type/timestamp.html.
var allowedTimestampFormats = []string{
"2006-1-2T15:4:5.999999999Z07:00", // RCF3339Nano with short date fields.
"2006-1-2t15:4:5.999999999Z07:00", // RFC3339Nano with short date fields and lower-case "t".
"2006-1-2 15:4:5.999999999", // space separated with no time zone
"2006-1-2", // date only
// Notable exception: time.Parse cannot handle: "2001-12-14 21:59:43.10 -5"
// from the set of examples.
}

// parseTimestamp parses s as a timestamp string and
// returns the timestamp and reports whether it succeeded.
// Timestamp formats are defined at http://yaml.org/type/timestamp.html
// Copied from yaml.v3.
func parseTimestamp(s string) (time.Time, bool) {
// TODO write code to check all the formats supported by
// http://yaml.org/type/timestamp.html instead of using time.Parse.

// Quick check: all date formats start with YYYY-.
i := 0
for ; i < len(s); i++ {
if c := s[i]; c < '0' || c > '9' {
break
}
}
if i != 4 || i == len(s) || s[i] != '-' {
return time.Time{}, false
}
for _, format := range allowedTimestampFormats {
if t, err := time.Parse(format, s); err == nil {
return t, true
}
}
return time.Time{}, false
}
10 changes: 10 additions & 0 deletions internal/command/select_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -218,4 +218,14 @@ octal: 8`)),
nil,
))

t.Run("Issue331 - YAML to JSON", runTest(
[]string{"-r", "yaml", "-w", "json"},
[]byte(`createdAt: 2023-06-13T20:19:48.531620935Z`),
newline([]byte(`{
"createdAt": "2023-06-13T20:19:48.531620935Z"
}`)),
nil,
nil,
))

}

0 comments on commit 20b46bc

Please sign in to comment.