Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

private/protocol: add support for UTC offset for IOS datetime formats #3960

Merged
merged 3 commits into from
Jun 17, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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