84c9110a24
Bumps [github.com/hashicorp/terraform-plugin-sdk/v2](https://github.com/hashicorp/terraform-plugin-sdk) from 2.24.1 to 2.26.0. - [Release notes](https://github.com/hashicorp/terraform-plugin-sdk/releases) - [Changelog](https://github.com/hashicorp/terraform-plugin-sdk/blob/main/CHANGELOG.md) - [Commits](https://github.com/hashicorp/terraform-plugin-sdk/compare/v2.24.1...v2.26.0) --- updated-dependencies: - dependency-name: github.com/hashicorp/terraform-plugin-sdk/v2 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com>
220 lines
6.4 KiB
Go
220 lines
6.4 KiB
Go
package stdlib
|
|
|
|
import (
|
|
"errors"
|
|
"strconv"
|
|
"time"
|
|
)
|
|
|
|
// This file inlines some RFC3339 parsing code that was added to the Go standard
|
|
// library's "time" package during the Go 1.20 development period but then
|
|
// reverted prior to release to follow the Go proposals process first.
|
|
//
|
|
// Our goal is to support only valid RFC3339 strings regardless of what version
|
|
// of Go is being used, because the Go stdlib is just an implementation detail
|
|
// of the cty stdlib and so these functions should not very their behavior
|
|
// significantly due to being compiled against a different Go version.
|
|
//
|
|
// These inline copies of the code from upstream should likely stay here
|
|
// indefinitely even if functionality like this _is_ accepted in a later version
|
|
// of Go, because this now defines cty's definition of RFC3339 parsing as
|
|
// intentionally independent of Go's.
|
|
|
|
func parseStrictRFC3339(str string) (time.Time, error) {
|
|
t, ok := parseRFC3339(str)
|
|
if !ok {
|
|
// If parsing failed then we'll try to use time.Parse to gather up a
|
|
// helpful error object.
|
|
_, err := time.Parse(time.RFC3339, str)
|
|
if err != nil {
|
|
return time.Time{}, err
|
|
}
|
|
|
|
// The parse template syntax cannot correctly validate RFC 3339.
|
|
// Explicitly check for cases that Parse is unable to validate for.
|
|
// See https://go.dev/issue/54580.
|
|
num2 := func(str string) byte { return 10*(str[0]-'0') + (str[1] - '0') }
|
|
switch {
|
|
case str[len("2006-01-02T")+1] == ':': // hour must be two digits
|
|
return time.Time{}, &time.ParseError{
|
|
Layout: time.RFC3339,
|
|
Value: str,
|
|
LayoutElem: "15",
|
|
ValueElem: str[len("2006-01-02T"):][:1],
|
|
Message: ": hour must have two digits",
|
|
}
|
|
case str[len("2006-01-02T15:04:05")] == ',': // sub-second separator must be a period
|
|
return time.Time{}, &time.ParseError{
|
|
Layout: time.RFC3339,
|
|
Value: str,
|
|
LayoutElem: ".",
|
|
ValueElem: ",",
|
|
Message: ": sub-second separator must be a period",
|
|
}
|
|
case str[len(str)-1] != 'Z':
|
|
switch {
|
|
case num2(str[len(str)-len("07:00"):]) >= 24: // timezone hour must be in range
|
|
return time.Time{}, &time.ParseError{
|
|
Layout: time.RFC3339,
|
|
Value: str,
|
|
LayoutElem: "Z07:00",
|
|
ValueElem: str[len(str)-len("Z07:00"):],
|
|
Message: ": timezone hour out of range",
|
|
}
|
|
case num2(str[len(str)-len("00"):]) >= 60: // timezone minute must be in range
|
|
return time.Time{}, &time.ParseError{
|
|
Layout: time.RFC3339,
|
|
Value: str,
|
|
LayoutElem: "Z07:00",
|
|
ValueElem: str[len(str)-len("Z07:00"):],
|
|
Message: ": timezone minute out of range",
|
|
}
|
|
}
|
|
default: // unknown error; should not occur
|
|
return time.Time{}, &time.ParseError{
|
|
Layout: time.RFC3339,
|
|
Value: str,
|
|
LayoutElem: time.RFC3339,
|
|
ValueElem: str,
|
|
Message: "",
|
|
}
|
|
}
|
|
}
|
|
return t, nil
|
|
}
|
|
|
|
func parseRFC3339(s string) (time.Time, bool) {
|
|
// parseUint parses s as an unsigned decimal integer and
|
|
// verifies that it is within some range.
|
|
// If it is invalid or out-of-range,
|
|
// it sets ok to false and returns the min value.
|
|
ok := true
|
|
parseUint := func(s string, min, max int) (x int) {
|
|
for _, c := range []byte(s) {
|
|
if c < '0' || '9' < c {
|
|
ok = false
|
|
return min
|
|
}
|
|
x = x*10 + int(c) - '0'
|
|
}
|
|
if x < min || max < x {
|
|
ok = false
|
|
return min
|
|
}
|
|
return x
|
|
}
|
|
|
|
// Parse the date and time.
|
|
if len(s) < len("2006-01-02T15:04:05") {
|
|
return time.Time{}, false
|
|
}
|
|
year := parseUint(s[0:4], 0, 9999) // e.g., 2006
|
|
month := parseUint(s[5:7], 1, 12) // e.g., 01
|
|
day := parseUint(s[8:10], 1, daysIn(time.Month(month), year)) // e.g., 02
|
|
hour := parseUint(s[11:13], 0, 23) // e.g., 15
|
|
min := parseUint(s[14:16], 0, 59) // e.g., 04
|
|
sec := parseUint(s[17:19], 0, 59) // e.g., 05
|
|
if !ok || !(s[4] == '-' && s[7] == '-' && s[10] == 'T' && s[13] == ':' && s[16] == ':') {
|
|
return time.Time{}, false
|
|
}
|
|
s = s[19:]
|
|
|
|
// Parse the fractional second.
|
|
var nsec int
|
|
if len(s) >= 2 && s[0] == '.' && isDigit(s, 1) {
|
|
n := 2
|
|
for ; n < len(s) && isDigit(s, n); n++ {
|
|
}
|
|
nsec, _, _ = parseNanoseconds(s, n)
|
|
s = s[n:]
|
|
}
|
|
|
|
// Parse the time zone.
|
|
loc := time.UTC
|
|
if len(s) != 1 || s[0] != 'Z' {
|
|
if len(s) != len("-07:00") {
|
|
return time.Time{}, false
|
|
}
|
|
hr := parseUint(s[1:3], 0, 23) // e.g., 07
|
|
mm := parseUint(s[4:6], 0, 59) // e.g., 00
|
|
if !ok || !((s[0] == '-' || s[0] == '+') && s[3] == ':') {
|
|
return time.Time{}, false
|
|
}
|
|
zoneOffsetSecs := (hr*60 + mm) * 60
|
|
if s[0] == '-' {
|
|
zoneOffsetSecs = -zoneOffsetSecs
|
|
}
|
|
loc = time.FixedZone("", zoneOffsetSecs)
|
|
}
|
|
t := time.Date(year, time.Month(month), day, hour, min, sec, nsec, loc)
|
|
|
|
return t, true
|
|
}
|
|
|
|
func isDigit(s string, i int) bool {
|
|
if len(s) <= i {
|
|
return false
|
|
}
|
|
c := s[i]
|
|
return '0' <= c && c <= '9'
|
|
}
|
|
|
|
func parseNanoseconds(value string, nbytes int) (ns int, rangeErrString string, err error) {
|
|
if value[0] != '.' && value[0] != ',' {
|
|
err = errBadTimestamp
|
|
return
|
|
}
|
|
if nbytes > 10 {
|
|
value = value[:10]
|
|
nbytes = 10
|
|
}
|
|
if ns, err = strconv.Atoi(value[1:nbytes]); err != nil {
|
|
return
|
|
}
|
|
if ns < 0 {
|
|
rangeErrString = "fractional second"
|
|
return
|
|
}
|
|
// We need nanoseconds, which means scaling by the number
|
|
// of missing digits in the format, maximum length 10.
|
|
scaleDigits := 10 - nbytes
|
|
for i := 0; i < scaleDigits; i++ {
|
|
ns *= 10
|
|
}
|
|
return
|
|
}
|
|
|
|
// These are internal errors used by the date parsing code and are not ever
|
|
// returned by public functions.
|
|
var errBadTimestamp = errors.New("bad value for field")
|
|
|
|
// daysBefore[m] counts the number of days in a non-leap year
|
|
// before month m begins. There is an entry for m=12, counting
|
|
// the number of days before January of next year (365).
|
|
var daysBefore = [...]int32{
|
|
0,
|
|
31,
|
|
31 + 28,
|
|
31 + 28 + 31,
|
|
31 + 28 + 31 + 30,
|
|
31 + 28 + 31 + 30 + 31,
|
|
31 + 28 + 31 + 30 + 31 + 30,
|
|
31 + 28 + 31 + 30 + 31 + 30 + 31,
|
|
31 + 28 + 31 + 30 + 31 + 30 + 31 + 31,
|
|
31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30,
|
|
31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31,
|
|
31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30,
|
|
31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30 + 31,
|
|
}
|
|
|
|
func daysIn(m time.Month, year int) int {
|
|
if m == time.February && isLeap(year) {
|
|
return 29
|
|
}
|
|
return int(daysBefore[m] - daysBefore[m-1])
|
|
}
|
|
|
|
func isLeap(year int) bool {
|
|
return year%4 == 0 && (year%100 != 0 || year%400 == 0)
|
|
}
|