diff --git a/README.md b/README.md index 319f303..e98fc05 100644 --- a/README.md +++ b/README.md @@ -227,6 +227,8 @@ syntax. **Note**: this operation is done in-place. If you want to preserve the original string(s), pair the transform with `shift`. This transform also supports the `$now` operator for `inputFormat`, which will set the current timestamp at the specified path, formatted according to the `outputFormat`. +`$unix` is supported for both input and output formats as a Unix time, the +number of seconds elapsed since January 1, 1970 UTC as an integer string. ```javascript { "operation": "timestamp", @@ -237,7 +239,11 @@ timestamp at the specified path, formatted according to the `outputFormat`. "nowTimestamp": { "inputFormat": "$now", "outputFormat": "2006-01-02T15:04:05-0700" - } + }, + "epochTimestamp": { + "inputFormat": "2006-01-02T15:04:05-0700", + "outputFormat": "$unix" + } } ``` diff --git a/transform/timestamp.go b/transform/timestamp.go index b0632d5..2beccda 100644 --- a/transform/timestamp.go +++ b/transform/timestamp.go @@ -13,6 +13,8 @@ import ( // This is necessary for testing purposes var now = time.Now +const unixFormat = "$unix" + // Timestamp parses and formats timestamp strings using the golang syntax func Timestamp(spec *Config, data []byte) ([]byte, error) { for k, v := range *spec.Spec { @@ -96,10 +98,29 @@ func Timestamp(spec *Config, data []byte) ([]byte, error) { // parseAndFormatValue generates a properly formatted timestamp func parseAndFormatValue(inputFormat, outputFormat, unformattedItem string) (string, error) { - parsedItem, err := time.Parse(inputFormat, unformattedItem) - if err != nil { - return "", err + var ( + parsedItem time.Time + formattedItem string + err error + ) + + if inputFormat == unixFormat { + i, err := strconv.ParseInt(unformattedItem, 10, 64) + if err != nil { + return "", err + } + parsedItem = time.Unix(i, 0) + } else { + parsedItem, err = time.Parse(inputFormat, unformattedItem) + if err != nil { + return "", err + } + } + + if outputFormat == unixFormat { + formattedItem = strconv.FormatInt(parsedItem.Unix(), 10) + } else { + formattedItem = parsedItem.Format(outputFormat) } - formattedItem := strings.Join([]string{"\"", parsedItem.Format(outputFormat), "\""}, "") - return formattedItem, nil + return strings.Join([]string{"\"", formattedItem, "\""}, ""), nil } diff --git a/transform/timestamp_test.go b/transform/timestamp_test.go index e9e07f9..5367f52 100644 --- a/transform/timestamp_test.go +++ b/transform/timestamp_test.go @@ -169,6 +169,7 @@ func TestParseAndFormatValue(t *testing.T) { {time.UnixDate, "\"Fri Jul 21 08:15:27 +0100 2017\""}, {time.RFC3339, "\"2017-07-21T08:15:27+01:00\""}, {time.StampNano, "\"Jul 21 08:15:27.000000000\""}, + {"$unix", "\"1500621327\""}, } for _, testItem := range parseAndFormatTests { actual, _ := parseAndFormatValue(inputFormat, testItem.outputFormat, inputTimestamp) @@ -179,3 +180,27 @@ func TestParseAndFormatValue(t *testing.T) { } } } + +func TestParseAndFormatValueOutputUnix(t *testing.T) { + parseAndFormatTests := []struct { + inputFormat string + inputTimestamp string + expectedOutput string + }{ + // test against a sampling of common formats + {"2006-01-02T15:04:05-0700", "2017-07-21T08:15:27+0100", "\"1500621327\""}, + {"January _2, 2006", "July 21, 2017", "\"1500595200\""}, + {time.ANSIC, "Fri Jul 21 08:15:27 2017", "\"1500624927\""}, + {time.UnixDate, "Fri Jul 21 08:15:27 GMT 2017", "\"1500624927\""}, + {time.RFC3339, "2017-07-21T08:15:27+01:00", "\"1500621327\""}, + {"$unix", "1500621327", "\"1500621327\""}, + } + for _, testItem := range parseAndFormatTests { + actual, _ := parseAndFormatValue(testItem.inputFormat, "$unix", testItem.inputTimestamp) + if actual != testItem.expectedOutput { + t.Error("Error data does not match expectation.", testItem.inputFormat) + t.Log("Expected: ", testItem.expectedOutput) + t.Log("Actual: ", actual) + } + } +}