Skip to content

Commit

Permalink
private/protocol: add support for UTC offset for IOS datetime formats (
Browse files Browse the repository at this point in the history
…#3960)

Updates the SDK's parsing of ISO8601 based datetime formats to support UTC
offsets.

Related to aws/smithy-go#307
  • Loading branch information
jasdel committed Jun 17, 2021
1 parent 56a221f commit 06c411b
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 8 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG_PENDING.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@
### SDK Enhancements

### SDK Bugs
* `private/protocol`: Add support for UTC offset for ISO8601 datetime formats ([#3960](https://github.com/aws/aws-sdk-go/pull/3960))
* Updates the SDK's parsing of ISO8601 date time formats to support UTC offsets.
57 changes: 52 additions & 5 deletions private/protocol/timestamp.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package protocol

import (
"bytes"
"fmt"
"math"
"strconv"
"time"
Expand All @@ -19,7 +21,9 @@ const (
// Output time is intended to not contain decimals
const (
// RFC 7231#section-7.1.1.1 timetamp format. e.g Tue, 29 Apr 2014 18:30:38 GMT
RFC822TimeFormat = "Mon, 2 Jan 2006 15:04:05 GMT"
RFC822TimeFormat = "Mon, 2 Jan 2006 15:04:05 GMT"
rfc822TimeFormatSingleDigitDay = "Mon, _2 Jan 2006 15:04:05 GMT"
rfc822TimeFormatSingleDigitDayTwoDigitYear = "Mon, _2 Jan 06 15:04:05 GMT"

// This format is used for output time without seconds precision
RFC822OutputTimeFormat = "Mon, 02 Jan 2006 15:04:05 GMT"
Expand Down Expand Up @@ -67,10 +71,20 @@ func FormatTime(name string, t time.Time) string {
// the time if it was able to be parsed, and fails otherwise.
func ParseTime(formatName, value string) (time.Time, error) {
switch formatName {
case RFC822TimeFormatName:
return time.Parse(RFC822TimeFormat, value)
case ISO8601TimeFormatName:
return time.Parse(ISO8601TimeFormat, value)
case RFC822TimeFormatName: // Smithy HTTPDate format
return tryParse(value,
RFC822TimeFormat,
rfc822TimeFormatSingleDigitDay,
rfc822TimeFormatSingleDigitDayTwoDigitYear,
time.RFC850,
time.ANSIC,
)
case ISO8601TimeFormatName: // Smithy DateTime format
return tryParse(value,
ISO8601TimeFormat,
time.RFC3339Nano,
time.RFC3339,
)
case UnixTimeFormatName:
v, err := strconv.ParseFloat(value, 64)
_, dec := math.Modf(v)
Expand All @@ -83,3 +97,36 @@ func ParseTime(formatName, value string) (time.Time, error) {
panic("unknown timestamp format name, " + formatName)
}
}

func tryParse(v string, formats ...string) (time.Time, error) {
var errs parseErrors
for _, f := range formats {
t, err := time.Parse(f, v)
if err != nil {
errs = append(errs, parseError{
Format: f,
Err: err,
})
continue
}
return t, nil
}

return time.Time{}, fmt.Errorf("unable to parse time string, %v", errs)
}

type parseErrors []parseError

func (es parseErrors) Error() string {
var s bytes.Buffer
for _, e := range es {
fmt.Fprintf(&s, "\n * %q: %v", e.Format, e.Err)
}

return "parse errors:" + s.String()
}

type parseError struct {
Format string
Err error
}
31 changes: 28 additions & 3 deletions private/protocol/timestamp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,21 +80,46 @@ func TestParseTime(t *testing.T) {
input: "946845296.1229999",
expectedOutput: time.Date(2000, time.January, 2, 20, 34, 56, .123e9, time.UTC),
},
"ISO8601Test1": {
"ISO8601Test milliseconds": {
formatName: ISO8601TimeFormatName,
input: "2000-01-02T20:34:56.123Z",
expectedOutput: time.Date(2000, time.January, 2, 20, 34, 56, .123e9, time.UTC),
},
"ISO8601Test2": {
"ISO8601Test nanoseconds": {
formatName: ISO8601TimeFormatName,
input: "2000-01-02T20:34:56.123456789Z",
expectedOutput: time.Date(2000, time.January, 2, 20, 34, 56, .123456789e9, time.UTC),
},
"RFC822Test1": {
"ISO8601Test millisecond utc offset": {
formatName: ISO8601TimeFormatName,
input: "2000-01-02T20:34:56.123-07:00",
expectedOutput: time.Date(2000, time.January, 3, 3, 34, 56, .123e9, time.UTC),
},
"ISO8601Test millisecond positive utc offset": {
formatName: ISO8601TimeFormatName,
input: "2000-01-02T20:34:56.123+07:00",
expectedOutput: time.Date(2000, time.January, 2, 13, 34, 56, .123e9, time.UTC),
},
"ISO8601Test nanosecond utc offset": {
formatName: ISO8601TimeFormatName,
input: "2000-01-02T20:34:56.123456789-07:00",
expectedOutput: time.Date(2000, time.January, 3, 3, 34, 56, .123456789e9, time.UTC),
},
"RFC822Test single digit day": {
formatName: RFC822TimeFormatName,
input: "Sun, 2 Jan 2000 20:34:56 GMT",
expectedOutput: time.Date(2000, time.January, 2, 20, 34, 56, 0, time.UTC),
},
"RFC822Test two digit day": {
formatName: RFC822TimeFormatName,
input: "Sun, 02 Jan 2000 20:34:56 GMT",
expectedOutput: time.Date(2000, time.January, 2, 20, 34, 56, 0, time.UTC),
},
"RFC822Test two digit day year": {
formatName: RFC822TimeFormatName,
input: "Sun, 2 Jan 00 20:34:56 GMT",
expectedOutput: time.Date(2000, time.January, 2, 20, 34, 56, 0, time.UTC),
},
}

for name, c := range cases {
Expand Down

0 comments on commit 06c411b

Please sign in to comment.