Skip to content

Commit 81ab069

Browse files
skotambkarjasdel
authored andcommitted
private/protocol: Add1 support for parsing fractional timestamp (#367)
Ports the V1 SDK's ability to parse fractional timestamp . Adds timestamp_test to assert behavior. Fixes #365
1 parent f63f9f6 commit 81ab069

File tree

5 files changed

+185
-8
lines changed

5 files changed

+185
-8
lines changed

CHANGELOG_PENDING.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
### SDK Features
22

33
### SDK Enhancements
4+
* `private/protocol`: Add support for parsing fractional timestamp ([#367](https://github.com/aws/aws-sdk-go-v2/pull/367))
5+
* Fixes the SDK's ability to parse fractional unix timestamp values and adds tests.
6+
* Fixes [#365](https://github.com/aws/aws-sdk-go-v2/issues/365)
47

58
### SDK Bugs
69

internal/sdkmath/floor.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// +build go1.10
2+
3+
package sdkmath
4+
5+
import "math"
6+
7+
// Round returns the nearest integer, rounding half away from zero.
8+
//
9+
// Special cases are:
10+
// Round(±0) = ±0
11+
// Round(±Inf) = ±Inf
12+
// Round(NaN) = NaN
13+
func Round(x float64) float64 {
14+
return math.Round(x)
15+
}

internal/sdkmath/floor_go1.9.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// +build !go1.10
2+
3+
package sdkmath
4+
5+
import "math"
6+
7+
// Copied from the Go standard library's (Go 1.12) math/floor.go for use in
8+
// Go version prior to Go 1.10.
9+
const (
10+
uvone = 0x3FF0000000000000
11+
mask = 0x7FF
12+
shift = 64 - 11 - 1
13+
bias = 1023
14+
signMask = 1 << 63
15+
fracMask = 1<<shift - 1
16+
)
17+
18+
// Round returns the nearest integer, rounding half away from zero.
19+
//
20+
// Special cases are:
21+
// Round(±0) = ±0
22+
// Round(±Inf) = ±Inf
23+
// Round(NaN) = NaN
24+
//
25+
// Copied from the Go standard library's (Go 1.12) math/floor.go for use in
26+
// Go version prior to Go 1.10.
27+
func Round(x float64) float64 {
28+
// Round is a faster implementation of:
29+
//
30+
// func Round(x float64) float64 {
31+
// t := Trunc(x)
32+
// if Abs(x-t) >= 0.5 {
33+
// return t + Copysign(1, x)
34+
// }
35+
// return t
36+
// }
37+
bits := math.Float64bits(x)
38+
e := uint(bits>>shift) & mask
39+
if e < bias {
40+
// Round abs(x) < 1 including denormals.
41+
bits &= signMask // +-0
42+
if e == bias-1 {
43+
bits |= uvone // +-1
44+
}
45+
} else if e < bias+shift {
46+
// Round any abs(x) >= 1 containing a fractional component [0,1).
47+
//
48+
// Numbers with larger exponents are returned unchanged since they
49+
// must be either an integer, infinity, or NaN.
50+
const half = 1 << (shift - 1)
51+
e -= bias
52+
bits += half >> e
53+
bits &^= fracMask >> e
54+
}
55+
return math.Float64frombits(bits)
56+
}

private/protocol/timestamp.go

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,11 @@ package protocol
22

33
import (
44
"fmt"
5+
"math"
56
"strconv"
67
"time"
8+
9+
"github.com/aws/aws-sdk-go-v2/internal/sdkmath"
710
)
811

912
// Names of time formats supported by the SDK
@@ -18,8 +21,14 @@ const (
1821
// RFC 7231#section-7.1.1.1 timetamp format. e.g Tue, 29 Apr 2014 18:30:38 GMT
1922
RFC822TimeFormat = "Mon, 2 Jan 2006 15:04:05 GMT"
2023

21-
// RFC3339 a subset of the ISO8601 timestamp format. e.g 2014-04-29T18:30:38Z
22-
ISO8601TimeFormat = "2006-01-02T15:04:05Z"
24+
// RFC3339 a subset of the ISO8601 timestamp format. e.g 2014-04-29T18:30:38.999999999Z
25+
ISO8601TimeFormat = "2006-01-02T15:04:05.999999999Z"
26+
27+
// RFC Output TimeStamp format is used for output time without seconds precision
28+
RFC822OutputTimeFormat = "Mon, 02 Jan 2006 15:04:05 GMT"
29+
30+
// ISO output TimeStamp format is used for output time without seconds precision
31+
ISO8601OutputTimeFormat = "2006-01-02T15:04:05Z"
2332
)
2433

2534
// IsKnownTimestampFormat returns if the timestamp format name
@@ -38,18 +47,18 @@ func IsKnownTimestampFormat(name string) bool {
3847
}
3948

4049
// FormatTime returns a string value of the time.
41-
func FormatTime(name string, t time.Time) (string,error) {
50+
func FormatTime(name string, t time.Time) (string, error) {
4251
t = t.UTC()
4352

4453
switch name {
4554
case RFC822TimeFormatName:
46-
return t.Format(RFC822TimeFormat), nil
55+
return t.Format(RFC822OutputTimeFormat), nil
4756
case ISO8601TimeFormatName:
48-
return t.Format(ISO8601TimeFormat), nil
57+
return t.Format(ISO8601OutputTimeFormat), nil
4958
case UnixTimeFormatName:
5059
return strconv.FormatInt(t.Unix(), 10), nil
5160
default:
52-
return "",fmt.Errorf("unknown timestamp format name, " + name)
61+
return "", fmt.Errorf("unknown timestamp format name, " + name)
5362
}
5463
}
5564

@@ -63,11 +72,13 @@ func ParseTime(formatName, value string) (time.Time, error) {
6372
return time.Parse(ISO8601TimeFormat, value)
6473
case UnixTimeFormatName:
6574
v, err := strconv.ParseFloat(value, 64)
75+
_, dec := math.Modf(v)
76+
dec = sdkmath.Round(dec*1e3) / 1e3 //Rounds 0.1229999 to 0.123
6677
if err != nil {
6778
return time.Time{}, err
6879
}
69-
t := time.Unix(int64(v), 0)
70-
return t.UTC(),nil
80+
t := time.Unix(int64(v), int64(dec*(1e9)))
81+
return t.UTC(), nil
7182

7283
default:
7384
panic("unknown timestamp format name, " + formatName)

private/protocol/timestamp_test.go

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
// +build go1.7
2+
3+
package protocol
4+
5+
import (
6+
"testing"
7+
"time"
8+
)
9+
10+
func TestFormatTime(t *testing.T) {
11+
cases := map[string]struct {
12+
formatName string
13+
expectedOutput string
14+
input time.Time
15+
}{
16+
"UnixTest1": {
17+
formatName: UnixTimeFormatName,
18+
expectedOutput: "946845296",
19+
input: time.Date(2000, time.January, 2, 20, 34, 56, .123e9, time.UTC),
20+
},
21+
"ISO8601Test1": {
22+
formatName: ISO8601TimeFormatName,
23+
expectedOutput: "2000-01-02T20:34:56Z",
24+
input: time.Date(2000, time.January, 2, 20, 34, 56, .123e9, time.UTC),
25+
},
26+
"RFC822Test1": {
27+
formatName: RFC822TimeFormatName,
28+
expectedOutput: "Sun, 02 Jan 2000 20:34:56 GMT",
29+
input: time.Date(2000, time.January, 2, 20, 34, 56, 0, time.UTC),
30+
},
31+
}
32+
33+
for name, c := range cases {
34+
t.Run(name, func(t *testing.T) {
35+
if v, _ := FormatTime(c.formatName, c.input); v != c.expectedOutput {
36+
t.Errorf("input %v \n and output: %v, \n don't match for %s format ", c.input, c.expectedOutput, c.formatName)
37+
}
38+
})
39+
}
40+
}
41+
42+
func TestParseTime(t *testing.T) {
43+
44+
// Assert equals for input and output works for a precision upto three decimal places
45+
cases := map[string]struct {
46+
formatName, input string
47+
expectedOutput time.Time
48+
}{
49+
"UnixTest1": {
50+
formatName: UnixTimeFormatName,
51+
input: "946845296.123",
52+
expectedOutput: time.Date(2000, time.January, 2, 20, 34, 56, .123e9, time.UTC),
53+
},
54+
"UnixTest2": {
55+
formatName: UnixTimeFormatName,
56+
input: "946845296.12344",
57+
expectedOutput: time.Date(2000, time.January, 2, 20, 34, 56, .123e9, time.UTC),
58+
},
59+
"UnixTest3": {
60+
formatName: UnixTimeFormatName,
61+
input: "946845296.1229999",
62+
expectedOutput: time.Date(2000, time.January, 2, 20, 34, 56, .123e9, time.UTC),
63+
},
64+
"ISO8601Test1": {
65+
formatName: ISO8601TimeFormatName,
66+
input: "2000-01-02T20:34:56.123Z",
67+
expectedOutput: time.Date(2000, time.January, 2, 20, 34, 56, .123e9, time.UTC),
68+
},
69+
"ISO8601Test2": {
70+
formatName: ISO8601TimeFormatName,
71+
input: "2000-01-02T20:34:56.123456789Z",
72+
expectedOutput: time.Date(2000, time.January, 2, 20, 34, 56, .123456789e9, time.UTC),
73+
},
74+
"RFC822Test1": {
75+
formatName: RFC822TimeFormatName,
76+
input: "Sun, 2 Jan 2000 20:34:56 GMT",
77+
expectedOutput: time.Date(2000, time.January, 2, 20, 34, 56, 0, time.UTC),
78+
},
79+
}
80+
81+
for name, c := range cases {
82+
t.Run(name, func(t *testing.T) {
83+
timeVal, err := ParseTime(c.formatName, c.input)
84+
if err != nil {
85+
t.Errorf("unable to parse time, %v", err)
86+
}
87+
if timeVal.UTC() != c.expectedOutput {
88+
t.Errorf("input: %v \n and output time %v,\n don't match for %s format ", c.input, c.expectedOutput, c.formatName)
89+
}
90+
})
91+
}
92+
}

0 commit comments

Comments
 (0)