Compare commits

..

2 Commits

Author SHA1 Message Date
Bjørn Erik Pedersen f31dc0aaab
Adjust timezone logic
This commit adjusts the previous commit re. the new `ToTimeInDefaultLocationE` and related functions.

The functional change is that we don't default to any location if not provided from the caller.
This is in line with how `ToTime` worked before we started this, and even if the default behaviour may look weird in some cases, it will not break anything.

Most applications will want to use the new *InDefaultLocation functions and decide which default location to use:

```go
loc := time.Local
if config.Location != "" {
    loc = time.LoadLocation(config.Location)
}

t, err := StringToDateInDefaultLocation("2019-01-01", loc)

```

This commit also configure Travis to test on OSX and Windows in addition to Linux.
2019-05-31 17:19:31 +02:00
Heewa Barfchin b1aa5f0c52
Add ToTimeInDefaultLocation/E
Go's time parsing uses UTC when the format doesn't have a tiemzone, and
has even weirder behavior when it has a zone name but no numeric offset.
A caller to `cast.ToTime` won't know if the returned time was explicitly
in UTC, or defaulted there, so the caller cannot fix it. These new
functions allow a user to supply a different timezone to default to,
with nil using the local zone.
2019-05-31 11:32:53 +02:00
10 changed files with 799 additions and 770 deletions

View File

@ -1,28 +0,0 @@
name: Go
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
build:
strategy:
matrix:
go-version: [1.16.x, 1.17.x, 1.18.x]
os: [ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: ${{ matrix.go-version }}
- name: Build
run: go build -v ./...
- name: Test
run: go test -race -v ./...

21
.travis.yml Normal file
View File

@ -0,0 +1,21 @@
language: go
env:
- GO111MODULE=on
sudo: required
go:
- "1.11.x"
- "1.12.x"
- tip
os:
- linux
- osx
- windows
matrix:
allow_failures:
- go: tip
exclude:
- os: windows
go: tip
fast_finish: true
script:
- if [[ "$TRAVIS_OS_NAME" == "windows" ]]; then go test -v -race ./...; else make check; fi

View File

@ -1,15 +1,15 @@
GOVERSION := $(shell go version | cut -d ' ' -f 3 | cut -d '.' -f 2) GOVERSION := $(shell go version | cut -d ' ' -f 3 | cut -d '.' -f 2)
.PHONY: check fmt lint test test-race vet test-cover-html help .PHONY: check fmt test test-race vet test-cover-html help
.DEFAULT_GOAL := help .DEFAULT_GOAL := help
check: test-race fmt vet lint ## Run tests and linters check: test-race fmt vet ## Run tests and linters
test: ## Run tests test: ## Run tests
go test ./... go test ./...
test-race: ## Run tests with race detector test-race: ## Run tests with race detector
go test -race ./... go test -v -race ./...
fmt: ## Run gofmt linter fmt: ## Run gofmt linter
ifeq "$(GOVERSION)" "12" ifeq "$(GOVERSION)" "12"
@ -20,12 +20,6 @@ ifeq "$(GOVERSION)" "12"
done done
endif endif
lint: ## Run golint linter
@for d in `go list` ; do \
if [ "`golint $$d | tee /dev/stderr`" ]; then \
echo "^ golint errors!" && echo && exit 1; \
fi \
done
vet: ## Run go vet linter vet: ## Run go vet linter
@if [ "`go vet | tee /dev/stderr`" ]; then \ @if [ "`go vet | tee /dev/stderr`" ]; then \

View File

@ -1,8 +1,8 @@
cast cast
==== ====
[![GoDoc](https://godoc.org/git.internal/re/cast?status.svg)](https://godoc.org/git.internal/re/cast) [![GoDoc](https://godoc.org/github.com/spf13/cast?status.svg)](https://godoc.org/github.com/spf13/cast)
[![Build Status](https://git.internal/re/cast/actions/workflows/go.yml/badge.svg)](https://git.internal/re/cast/actions/workflows/go.yml) [![Build Status](https://api.travis-ci.org/spf13/cast.svg?branch=master)](https://travis-ci.org/spf13/cast)
[![Go Report Card](https://goreportcard.com/badge/git.internal/re/cast)](https://goreportcard.com/report/git.internal/re/cast) [![Go Report Card](https://goreportcard.com/badge/github.com/spf13/cast)](https://goreportcard.com/report/github.com/spf13/cast)
Easy and safe casting from one type to another in Go Easy and safe casting from one type to another in Go

View File

@ -20,6 +20,9 @@ func ToTime(i interface{}) time.Time {
return v return v
} }
// ToTimeInDefaultLocationE casts an empty interface to time.Time,
// interpreting inputs without a timezone to be in the given location.
// To fall back to the local timezone, use time.Local as the last argument.
func ToTimeInDefaultLocation(i interface{}, location *time.Location) time.Time { func ToTimeInDefaultLocation(i interface{}, location *time.Location) time.Time {
v, _ := ToTimeInDefaultLocationE(i, location) v, _ := ToTimeInDefaultLocationE(i, location)
return v return v

File diff suppressed because it is too large Load Diff

392
caste.go
View File

@ -20,12 +20,12 @@ var errNegativeNotAllowed = errors.New("unable to cast negative value")
// ToTimeE casts an interface to a time.Time type. // ToTimeE casts an interface to a time.Time type.
func ToTimeE(i interface{}) (tim time.Time, err error) { func ToTimeE(i interface{}) (tim time.Time, err error) {
return ToTimeInDefaultLocationE(i, time.UTC) return ToTimeInDefaultLocationE(i, nil)
} }
// ToTimeInDefaultLocationE casts an empty interface to time.Time, // ToTimeInDefaultLocationE casts an empty interface to time.Time,
// interpreting inputs without a timezone to be in the given location, // interpreting inputs without a timezone to be in the given location.
// or the local timezone if nil. // To fall back to the local timezone, use time.Local as the last argument.
func ToTimeInDefaultLocationE(i interface{}, location *time.Location) (tim time.Time, err error) { func ToTimeInDefaultLocationE(i interface{}, location *time.Location) (tim time.Time, err error) {
i = indirect(i) i = indirect(i)
@ -34,12 +34,6 @@ func ToTimeInDefaultLocationE(i interface{}, location *time.Location) (tim time.
return v, nil return v, nil
case string: case string:
return StringToDateInDefaultLocation(v, location) return StringToDateInDefaultLocation(v, location)
case json.Number:
s, err1 := ToInt64E(v)
if err1 != nil {
return time.Time{}, fmt.Errorf("unable to cast %#v of type %T to Time", i, i)
}
return time.Unix(s, 0), nil
case int: case int:
return time.Unix(int64(v), 0), nil return time.Unix(int64(v), 0), nil
case int64: case int64:
@ -77,11 +71,6 @@ func ToDurationE(i interface{}) (d time.Duration, err error) {
d, err = time.ParseDuration(s + "ns") d, err = time.ParseDuration(s + "ns")
} }
return return
case json.Number:
var v float64
v, err = s.Float64()
d = time.Duration(v)
return
default: default:
err = fmt.Errorf("unable to cast %#v of type %T to Duration", i, i) err = fmt.Errorf("unable to cast %#v of type %T to Duration", i, i)
return return
@ -104,12 +93,6 @@ func ToBoolE(i interface{}) (bool, error) {
return false, nil return false, nil
case string: case string:
return strconv.ParseBool(i.(string)) return strconv.ParseBool(i.(string))
case json.Number:
v, err := ToInt64E(b)
if err == nil {
return v != 0, nil
}
return false, fmt.Errorf("unable to cast %#v of type %T to bool", i, i)
default: default:
return false, fmt.Errorf("unable to cast %#v of type %T to bool", i, i) return false, fmt.Errorf("unable to cast %#v of type %T to bool", i, i)
} }
@ -119,16 +102,13 @@ func ToBoolE(i interface{}) (bool, error) {
func ToFloat64E(i interface{}) (float64, error) { func ToFloat64E(i interface{}) (float64, error) {
i = indirect(i) i = indirect(i)
intv, ok := toInt(i)
if ok {
return float64(intv), nil
}
switch s := i.(type) { switch s := i.(type) {
case float64: case float64:
return s, nil return s, nil
case float32: case float32:
return float64(s), nil return float64(s), nil
case int:
return float64(s), nil
case int64: case int64:
return float64(s), nil return float64(s), nil
case int32: case int32:
@ -153,19 +133,11 @@ func ToFloat64E(i interface{}) (float64, error) {
return v, nil return v, nil
} }
return 0, fmt.Errorf("unable to cast %#v of type %T to float64", i, i) return 0, fmt.Errorf("unable to cast %#v of type %T to float64", i, i)
case json.Number:
v, err := s.Float64()
if err == nil {
return v, nil
}
return 0, fmt.Errorf("unable to cast %#v of type %T to float64", i, i)
case bool: case bool:
if s { if s {
return 1, nil return 1, nil
} }
return 0, nil return 0, nil
case nil:
return 0, nil
default: default:
return 0, fmt.Errorf("unable to cast %#v of type %T to float64", i, i) return 0, fmt.Errorf("unable to cast %#v of type %T to float64", i, i)
} }
@ -175,16 +147,13 @@ func ToFloat64E(i interface{}) (float64, error) {
func ToFloat32E(i interface{}) (float32, error) { func ToFloat32E(i interface{}) (float32, error) {
i = indirect(i) i = indirect(i)
intv, ok := toInt(i)
if ok {
return float32(intv), nil
}
switch s := i.(type) { switch s := i.(type) {
case float64: case float64:
return float32(s), nil return float32(s), nil
case float32: case float32:
return s, nil return s, nil
case int:
return float32(s), nil
case int64: case int64:
return float32(s), nil return float32(s), nil
case int32: case int32:
@ -209,19 +178,11 @@ func ToFloat32E(i interface{}) (float32, error) {
return float32(v), nil return float32(v), nil
} }
return 0, fmt.Errorf("unable to cast %#v of type %T to float32", i, i) return 0, fmt.Errorf("unable to cast %#v of type %T to float32", i, i)
case json.Number:
v, err := s.Float64()
if err == nil {
return float32(v), nil
}
return 0, fmt.Errorf("unable to cast %#v of type %T to float32", i, i)
case bool: case bool:
if s { if s {
return 1, nil return 1, nil
} }
return 0, nil return 0, nil
case nil:
return 0, nil
default: default:
return 0, fmt.Errorf("unable to cast %#v of type %T to float32", i, i) return 0, fmt.Errorf("unable to cast %#v of type %T to float32", i, i)
} }
@ -231,12 +192,9 @@ func ToFloat32E(i interface{}) (float32, error) {
func ToInt64E(i interface{}) (int64, error) { func ToInt64E(i interface{}) (int64, error) {
i = indirect(i) i = indirect(i)
intv, ok := toInt(i)
if ok {
return int64(intv), nil
}
switch s := i.(type) { switch s := i.(type) {
case int:
return int64(s), nil
case int64: case int64:
return s, nil return s, nil
case int32: case int32:
@ -260,13 +218,11 @@ func ToInt64E(i interface{}) (int64, error) {
case float32: case float32:
return int64(s), nil return int64(s), nil
case string: case string:
v, err := strconv.ParseInt(trimZeroDecimal(s), 0, 0) v, err := strconv.ParseInt(s, 0, 0)
if err == nil { if err == nil {
return v, nil return v, nil
} }
return 0, fmt.Errorf("unable to cast %#v of type %T to int64", i, i) return 0, fmt.Errorf("unable to cast %#v of type %T to int64", i, i)
case json.Number:
return ToInt64E(string(s))
case bool: case bool:
if s { if s {
return 1, nil return 1, nil
@ -283,12 +239,9 @@ func ToInt64E(i interface{}) (int64, error) {
func ToInt32E(i interface{}) (int32, error) { func ToInt32E(i interface{}) (int32, error) {
i = indirect(i) i = indirect(i)
intv, ok := toInt(i)
if ok {
return int32(intv), nil
}
switch s := i.(type) { switch s := i.(type) {
case int:
return int32(s), nil
case int64: case int64:
return int32(s), nil return int32(s), nil
case int32: case int32:
@ -312,13 +265,11 @@ func ToInt32E(i interface{}) (int32, error) {
case float32: case float32:
return int32(s), nil return int32(s), nil
case string: case string:
v, err := strconv.ParseInt(trimZeroDecimal(s), 0, 0) v, err := strconv.ParseInt(s, 0, 0)
if err == nil { if err == nil {
return int32(v), nil return int32(v), nil
} }
return 0, fmt.Errorf("unable to cast %#v of type %T to int32", i, i) return 0, fmt.Errorf("unable to cast %#v of type %T to int32", i, i)
case json.Number:
return ToInt32E(string(s))
case bool: case bool:
if s { if s {
return 1, nil return 1, nil
@ -335,12 +286,9 @@ func ToInt32E(i interface{}) (int32, error) {
func ToInt16E(i interface{}) (int16, error) { func ToInt16E(i interface{}) (int16, error) {
i = indirect(i) i = indirect(i)
intv, ok := toInt(i)
if ok {
return int16(intv), nil
}
switch s := i.(type) { switch s := i.(type) {
case int:
return int16(s), nil
case int64: case int64:
return int16(s), nil return int16(s), nil
case int32: case int32:
@ -364,13 +312,11 @@ func ToInt16E(i interface{}) (int16, error) {
case float32: case float32:
return int16(s), nil return int16(s), nil
case string: case string:
v, err := strconv.ParseInt(trimZeroDecimal(s), 0, 0) v, err := strconv.ParseInt(s, 0, 0)
if err == nil { if err == nil {
return int16(v), nil return int16(v), nil
} }
return 0, fmt.Errorf("unable to cast %#v of type %T to int16", i, i) return 0, fmt.Errorf("unable to cast %#v of type %T to int16", i, i)
case json.Number:
return ToInt16E(string(s))
case bool: case bool:
if s { if s {
return 1, nil return 1, nil
@ -387,12 +333,9 @@ func ToInt16E(i interface{}) (int16, error) {
func ToInt8E(i interface{}) (int8, error) { func ToInt8E(i interface{}) (int8, error) {
i = indirect(i) i = indirect(i)
intv, ok := toInt(i)
if ok {
return int8(intv), nil
}
switch s := i.(type) { switch s := i.(type) {
case int:
return int8(s), nil
case int64: case int64:
return int8(s), nil return int8(s), nil
case int32: case int32:
@ -416,13 +359,11 @@ func ToInt8E(i interface{}) (int8, error) {
case float32: case float32:
return int8(s), nil return int8(s), nil
case string: case string:
v, err := strconv.ParseInt(trimZeroDecimal(s), 0, 0) v, err := strconv.ParseInt(s, 0, 0)
if err == nil { if err == nil {
return int8(v), nil return int8(v), nil
} }
return 0, fmt.Errorf("unable to cast %#v of type %T to int8", i, i) return 0, fmt.Errorf("unable to cast %#v of type %T to int8", i, i)
case json.Number:
return ToInt8E(string(s))
case bool: case bool:
if s { if s {
return 1, nil return 1, nil
@ -439,12 +380,9 @@ func ToInt8E(i interface{}) (int8, error) {
func ToIntE(i interface{}) (int, error) { func ToIntE(i interface{}) (int, error) {
i = indirect(i) i = indirect(i)
intv, ok := toInt(i)
if ok {
return intv, nil
}
switch s := i.(type) { switch s := i.(type) {
case int:
return s, nil
case int64: case int64:
return int(s), nil return int(s), nil
case int32: case int32:
@ -468,13 +406,11 @@ func ToIntE(i interface{}) (int, error) {
case float32: case float32:
return int(s), nil return int(s), nil
case string: case string:
v, err := strconv.ParseInt(trimZeroDecimal(s), 0, 0) v, err := strconv.ParseInt(s, 0, 0)
if err == nil { if err == nil {
return int(v), nil return int(v), nil
} }
return 0, fmt.Errorf("unable to cast %#v of type %T to int64", i, i) return 0, fmt.Errorf("unable to cast %#v of type %T to int", i, i)
case json.Number:
return ToIntE(string(s))
case bool: case bool:
if s { if s {
return 1, nil return 1, nil
@ -491,26 +427,18 @@ func ToIntE(i interface{}) (int, error) {
func ToUintE(i interface{}) (uint, error) { func ToUintE(i interface{}) (uint, error) {
i = indirect(i) i = indirect(i)
intv, ok := toInt(i)
if ok {
if intv < 0 {
return 0, errNegativeNotAllowed
}
return uint(intv), nil
}
switch s := i.(type) { switch s := i.(type) {
case string: case string:
v, err := strconv.ParseInt(trimZeroDecimal(s), 0, 0) v, err := strconv.ParseUint(s, 0, 0)
if err == nil { if err == nil {
if v < 0 {
return 0, errNegativeNotAllowed
}
return uint(v), nil return uint(v), nil
} }
return 0, fmt.Errorf("unable to cast %#v of type %T to uint", i, i) return 0, fmt.Errorf("unable to cast %#v to uint: %s", i, err)
case json.Number: case int:
return ToUintE(string(s)) if s < 0 {
return 0, errNegativeNotAllowed
}
return uint(s), nil
case int64: case int64:
if s < 0 { if s < 0 {
return 0, errNegativeNotAllowed return 0, errNegativeNotAllowed
@ -567,26 +495,18 @@ func ToUintE(i interface{}) (uint, error) {
func ToUint64E(i interface{}) (uint64, error) { func ToUint64E(i interface{}) (uint64, error) {
i = indirect(i) i = indirect(i)
intv, ok := toInt(i)
if ok {
if intv < 0 {
return 0, errNegativeNotAllowed
}
return uint64(intv), nil
}
switch s := i.(type) { switch s := i.(type) {
case string: case string:
v, err := strconv.ParseInt(trimZeroDecimal(s), 0, 0) v, err := strconv.ParseUint(s, 0, 64)
if err == nil { if err == nil {
if v < 0 { return v, nil
}
return 0, fmt.Errorf("unable to cast %#v to uint64: %s", i, err)
case int:
if s < 0 {
return 0, errNegativeNotAllowed return 0, errNegativeNotAllowed
} }
return uint64(v), nil return uint64(s), nil
}
return 0, fmt.Errorf("unable to cast %#v of type %T to uint64", i, i)
case json.Number:
return ToUint64E(string(s))
case int64: case int64:
if s < 0 { if s < 0 {
return 0, errNegativeNotAllowed return 0, errNegativeNotAllowed
@ -643,26 +563,18 @@ func ToUint64E(i interface{}) (uint64, error) {
func ToUint32E(i interface{}) (uint32, error) { func ToUint32E(i interface{}) (uint32, error) {
i = indirect(i) i = indirect(i)
intv, ok := toInt(i)
if ok {
if intv < 0 {
return 0, errNegativeNotAllowed
}
return uint32(intv), nil
}
switch s := i.(type) { switch s := i.(type) {
case string: case string:
v, err := strconv.ParseInt(trimZeroDecimal(s), 0, 0) v, err := strconv.ParseUint(s, 0, 32)
if err == nil { if err == nil {
if v < 0 {
return 0, errNegativeNotAllowed
}
return uint32(v), nil return uint32(v), nil
} }
return 0, fmt.Errorf("unable to cast %#v of type %T to uint32", i, i) return 0, fmt.Errorf("unable to cast %#v to uint32: %s", i, err)
case json.Number: case int:
return ToUint32E(string(s)) if s < 0 {
return 0, errNegativeNotAllowed
}
return uint32(s), nil
case int64: case int64:
if s < 0 { if s < 0 {
return 0, errNegativeNotAllowed return 0, errNegativeNotAllowed
@ -719,26 +631,18 @@ func ToUint32E(i interface{}) (uint32, error) {
func ToUint16E(i interface{}) (uint16, error) { func ToUint16E(i interface{}) (uint16, error) {
i = indirect(i) i = indirect(i)
intv, ok := toInt(i)
if ok {
if intv < 0 {
return 0, errNegativeNotAllowed
}
return uint16(intv), nil
}
switch s := i.(type) { switch s := i.(type) {
case string: case string:
v, err := strconv.ParseInt(trimZeroDecimal(s), 0, 0) v, err := strconv.ParseUint(s, 0, 16)
if err == nil { if err == nil {
if v < 0 {
return 0, errNegativeNotAllowed
}
return uint16(v), nil return uint16(v), nil
} }
return 0, fmt.Errorf("unable to cast %#v of type %T to uint16", i, i) return 0, fmt.Errorf("unable to cast %#v to uint16: %s", i, err)
case json.Number: case int:
return ToUint16E(string(s)) if s < 0 {
return 0, errNegativeNotAllowed
}
return uint16(s), nil
case int64: case int64:
if s < 0 { if s < 0 {
return 0, errNegativeNotAllowed return 0, errNegativeNotAllowed
@ -795,26 +699,18 @@ func ToUint16E(i interface{}) (uint16, error) {
func ToUint8E(i interface{}) (uint8, error) { func ToUint8E(i interface{}) (uint8, error) {
i = indirect(i) i = indirect(i)
intv, ok := toInt(i)
if ok {
if intv < 0 {
return 0, errNegativeNotAllowed
}
return uint8(intv), nil
}
switch s := i.(type) { switch s := i.(type) {
case string: case string:
v, err := strconv.ParseInt(trimZeroDecimal(s), 0, 0) v, err := strconv.ParseUint(s, 0, 8)
if err == nil { if err == nil {
if v < 0 {
return 0, errNegativeNotAllowed
}
return uint8(v), nil return uint8(v), nil
} }
return 0, fmt.Errorf("unable to cast %#v of type %T to uint8", i, i) return 0, fmt.Errorf("unable to cast %#v to uint8: %s", i, err)
case json.Number: case int:
return ToUint8E(string(s)) if s < 0 {
return 0, errNegativeNotAllowed
}
return uint8(s), nil
case int64: case int64:
if s < 0 { if s < 0 {
return 0, errNegativeNotAllowed return 0, errNegativeNotAllowed
@ -930,17 +826,15 @@ func ToStringE(i interface{}) (string, error) {
case int8: case int8:
return strconv.FormatInt(int64(s), 10), nil return strconv.FormatInt(int64(s), 10), nil
case uint: case uint:
return strconv.FormatUint(uint64(s), 10), nil return strconv.FormatInt(int64(s), 10), nil
case uint64: case uint64:
return strconv.FormatUint(uint64(s), 10), nil return strconv.FormatInt(int64(s), 10), nil
case uint32: case uint32:
return strconv.FormatUint(uint64(s), 10), nil return strconv.FormatInt(int64(s), 10), nil
case uint16: case uint16:
return strconv.FormatUint(uint64(s), 10), nil return strconv.FormatInt(int64(s), 10), nil
case uint8: case uint8:
return strconv.FormatUint(uint64(s), 10), nil return strconv.FormatInt(int64(s), 10), nil
case json.Number:
return s.String(), nil
case []byte: case []byte:
return string(s), nil return string(s), nil
case template.HTML: case template.HTML:
@ -1242,43 +1136,8 @@ func ToStringSliceE(i interface{}) ([]string, error) {
return a, nil return a, nil
case []string: case []string:
return v, nil return v, nil
case []int8:
for _, u := range v {
a = append(a, ToString(u))
}
return a, nil
case []int:
for _, u := range v {
a = append(a, ToString(u))
}
return a, nil
case []int32:
for _, u := range v {
a = append(a, ToString(u))
}
return a, nil
case []int64:
for _, u := range v {
a = append(a, ToString(u))
}
return a, nil
case []float32:
for _, u := range v {
a = append(a, ToString(u))
}
return a, nil
case []float64:
for _, u := range v {
a = append(a, ToString(u))
}
return a, nil
case string: case string:
return strings.Fields(v), nil return strings.Fields(v), nil
case []error:
for _, err := range i.([]error) {
a = append(a, err.Error())
}
return a, nil
case interface{}: case interface{}:
str, err := ToStringE(v) str, err := ToStringE(v)
if err != nil { if err != nil {
@ -1352,24 +1211,51 @@ func ToDurationSliceE(i interface{}) ([]time.Duration, error) {
// predefined list of formats. If no suitable format is found, an error is // predefined list of formats. If no suitable format is found, an error is
// returned. // returned.
func StringToDate(s string) (time.Time, error) { func StringToDate(s string) (time.Time, error) {
return parseDateWith(s, time.UTC, timeFormats) return parseDateWith(s, nil, timeFormats)
} }
// StringToDateInDefaultLocation casts an empty interface to a time.Time, // StringToDateInDefaultLocation to parse a string into a time.Time type using a
// interpreting inputs without a timezone to be in the given location, // predefined list of formats, interpreting inputs without a timezone to be in
// or the local timezone if nil. // the given location.
// To fall back to the local timezone, use time.Local as the last argument.
func StringToDateInDefaultLocation(s string, location *time.Location) (time.Time, error) { func StringToDateInDefaultLocation(s string, location *time.Location) (time.Time, error) {
return parseDateWith(s, location, timeFormats) return parseDateWith(s, location, timeFormats)
} }
func parseDateWith(s string, location *time.Location, formats []timeFormat) (d time.Time, e error) {
for _, format := range formats {
if d, e = time.Parse(format.format, s); e == nil {
// Some time formats have a zone name, but no offset, so it gets
// put in that zone name (not the default one passed in to us), but
// without that zone's offset. So set the location manually.
// Note that we only do this when we get a location in the new *InDefaultLocation
// variants to avoid breaking existing behaviour in ToTime, however
// weird that existing behaviour may be.
if location != nil && !format.hasNumericTimezone() {
year, month, day := d.Date()
hour, min, sec := d.Clock()
d = time.Date(year, month, day, hour, min, sec, d.Nanosecond(), location)
}
return
}
}
return d, fmt.Errorf("unable to parse date: %s", s)
}
type timeFormatType int type timeFormatType int
const ( const (
timeFormatNoTimezone timeFormatType = iota timeFormatShort timeFormatType = iota // time or date only, no timezone
timeFormatNoTimezone
// All below have some kind of timezone information, a name and/or offset.
timeFormatNamedTimezone timeFormatNamedTimezone
timeFormatNumericTimezone
// All below have what we consider to be solid timezone information.
timeFormatNumericAndNamedTimezone timeFormatNumericAndNamedTimezone
timeFormatTimeOnly timeFormatNumericTimezone
) )
type timeFormat struct { type timeFormat struct {
@ -1377,10 +1263,12 @@ type timeFormat struct {
typ timeFormatType typ timeFormatType
} }
func (f timeFormat) hasTimezone() bool { func (f timeFormat) hasNumericTimezone() bool {
// We don't include the formats with only named timezones, see return f.typ >= timeFormatNumericAndNamedTimezone
// https://github.com/golang/go/issues/19694#issuecomment-289103522 }
return f.typ >= timeFormatNumericTimezone && f.typ <= timeFormatNumericAndNamedTimezone
func (f timeFormat) hasAnyTimezone() bool {
return f.typ >= timeFormatNamedTimezone
} }
var ( var (
@ -1397,80 +1285,26 @@ var (
{"2006-01-02 15:04:05Z0700", timeFormatNumericTimezone}, // RFC3339 without T or timezone hh:mm colon {"2006-01-02 15:04:05Z0700", timeFormatNumericTimezone}, // RFC3339 without T or timezone hh:mm colon
{"2006-01-02 15:04:05", timeFormatNoTimezone}, {"2006-01-02 15:04:05", timeFormatNoTimezone},
{time.ANSIC, timeFormatNoTimezone}, {time.ANSIC, timeFormatNoTimezone},
{time.UnixDate, timeFormatNamedTimezone}, // Must try RubyDate before UnixDate, see:
// https://github.com/golang/go/issues/32358
{time.RubyDate, timeFormatNumericTimezone}, {time.RubyDate, timeFormatNumericTimezone},
{time.UnixDate, timeFormatNamedTimezone},
{"2006-01-02 15:04:05Z07:00", timeFormatNumericTimezone}, {"2006-01-02 15:04:05Z07:00", timeFormatNumericTimezone},
{"2006-01-02", timeFormatNoTimezone}, {"2006-01-02", timeFormatShort},
{"02 Jan 2006", timeFormatNoTimezone}, {"02 Jan 2006", timeFormatShort},
{"2006-01-02 15:04:05 -07:00", timeFormatNumericTimezone}, {"2006-01-02 15:04:05 -07:00", timeFormatNumericTimezone},
{"2006-01-02 15:04:05 -0700", timeFormatNumericTimezone}, {"2006-01-02 15:04:05 -0700", timeFormatNumericTimezone},
{time.Kitchen, timeFormatTimeOnly}, {time.Kitchen, timeFormatShort},
{time.Stamp, timeFormatTimeOnly}, {time.Stamp, timeFormatShort},
{time.StampMilli, timeFormatTimeOnly}, {time.StampMilli, timeFormatShort},
{time.StampMicro, timeFormatTimeOnly}, {time.StampMicro, timeFormatShort},
{time.StampNano, timeFormatTimeOnly}, {time.StampNano, timeFormatShort},
} }
) )
func parseDateWith(s string, location *time.Location, formats []timeFormat) (d time.Time, e error) {
for _, format := range formats {
if d, e = time.Parse(format.format, s); e == nil {
// Some time formats have a zone name, but no offset, so it gets
// put in that zone name (not the default one passed in to us), but
// without that zone's offset. So set the location manually.
if format.typ <= timeFormatNamedTimezone {
if location == nil {
location = time.Local
}
year, month, day := d.Date()
hour, min, sec := d.Clock()
d = time.Date(year, month, day, hour, min, sec, d.Nanosecond(), location)
}
return
}
}
return d, fmt.Errorf("unable to parse date: %s", s)
}
// jsonStringToObject attempts to unmarshall a string as JSON into // jsonStringToObject attempts to unmarshall a string as JSON into
// the object passed as pointer. // the object passed as pointer.
func jsonStringToObject(s string, v interface{}) error { func jsonStringToObject(s string, v interface{}) error {
data := []byte(s) data := []byte(s)
return json.Unmarshal(data, v) return json.Unmarshal(data, v)
} }
// toInt returns the int value of v if v or v's underlying type
// is an int.
// Note that this will return false for int64 etc. types.
func toInt(v interface{}) (int, bool) {
switch v := v.(type) {
case int:
return v, true
case time.Weekday:
return int(v), true
case time.Month:
return int(v), true
default:
return 0, false
}
}
func trimZeroDecimal(s string) string {
var foundZero bool
for i := len(s); i > 0; i-- {
switch s[i-1] {
case '.':
if foundZero {
return s[:i-1]
}
case '0':
foundZero = true
default:
return s
}
}
return s
}

14
go.mod
View File

@ -1,13 +1,7 @@
module git.internal/re/cast module github.com/spf13/cast
go 1.18
require github.com/frankban/quicktest v1.14.3
require ( require (
github.com/google/go-cmp v0.5.7 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/kr/pretty v0.3.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/kr/text v0.2.0 // indirect github.com/stretchr/testify v1.2.2
github.com/rogpeppe/go-internal v1.6.1 // indirect
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 // indirect
) )

24
go.sum
View File

@ -1,18 +1,6 @@
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=

View File

@ -1,27 +0,0 @@
// Code generated by "stringer -type timeFormatType"; DO NOT EDIT.
package cast
import "strconv"
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[timeFormatNoTimezone-0]
_ = x[timeFormatNamedTimezone-1]
_ = x[timeFormatNumericTimezone-2]
_ = x[timeFormatNumericAndNamedTimezone-3]
_ = x[timeFormatTimeOnly-4]
}
const _timeFormatType_name = "timeFormatNoTimezonetimeFormatNamedTimezonetimeFormatNumericTimezonetimeFormatNumericAndNamedTimezonetimeFormatTimeOnly"
var _timeFormatType_index = [...]uint8{0, 20, 43, 68, 101, 119}
func (i timeFormatType) String() string {
if i < 0 || i >= timeFormatType(len(_timeFormatType_index)-1) {
return "timeFormatType(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _timeFormatType_name[_timeFormatType_index[i]:_timeFormatType_index[i+1]]
}