Merge f31dc0aaab into 1ffadf5510
				
					
				
			This commit is contained in:
		
						commit
						352095c115
					
				@ -8,9 +8,14 @@ go:
 | 
			
		||||
  - tip
 | 
			
		||||
os:
 | 
			
		||||
  - linux
 | 
			
		||||
  - osx
 | 
			
		||||
  - windows
 | 
			
		||||
matrix:
 | 
			
		||||
  allow_failures:
 | 
			
		||||
    - go: tip
 | 
			
		||||
  exclude:
 | 
			
		||||
    - os: windows
 | 
			
		||||
      go: tip
 | 
			
		||||
  fast_finish: true
 | 
			
		||||
script:
 | 
			
		||||
  - make check
 | 
			
		||||
  - if [[ "$TRAVIS_OS_NAME" == "windows" ]]; then go test -v -race ./...; else make check; fi
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										12
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								Makefile
									
									
									
									
									
								
							@ -1,15 +1,15 @@
 | 
			
		||||
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
 | 
			
		||||
 | 
			
		||||
check: test-race fmt vet lint ## Run tests and linters
 | 
			
		||||
check: test-race fmt vet ## Run tests and linters
 | 
			
		||||
 | 
			
		||||
test: ## Run tests
 | 
			
		||||
	go test ./...
 | 
			
		||||
 | 
			
		||||
test-race: ## Run tests with race detector
 | 
			
		||||
	go test -race ./...
 | 
			
		||||
	go test -v -race ./...
 | 
			
		||||
 | 
			
		||||
fmt: ## Run gofmt linter
 | 
			
		||||
ifeq "$(GOVERSION)" "12"
 | 
			
		||||
@ -20,12 +20,6 @@ ifeq "$(GOVERSION)" "12"
 | 
			
		||||
	done
 | 
			
		||||
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
 | 
			
		||||
	@if [ "`go vet | tee /dev/stderr`" ]; then \
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										8
									
								
								cast.go
									
									
									
									
									
								
							
							
						
						
									
										8
									
								
								cast.go
									
									
									
									
									
								
							@ -20,6 +20,14 @@ func ToTime(i interface{}) time.Time {
 | 
			
		||||
	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 {
 | 
			
		||||
	v, _ := ToTimeInDefaultLocationE(i, location)
 | 
			
		||||
	return v
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ToDuration casts an interface to a time.Duration type.
 | 
			
		||||
func ToDuration(i interface{}) time.Duration {
 | 
			
		||||
	v, _ := ToDurationE(i)
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										149
									
								
								cast_test.go
									
									
									
									
									
								
							
							
						
						
									
										149
									
								
								cast_test.go
									
									
									
									
									
								
							@ -8,10 +8,12 @@ package cast
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"html/template"
 | 
			
		||||
	"path"
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
	"github.com/stretchr/testify/require"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestToUintE(t *testing.T) {
 | 
			
		||||
@ -1173,7 +1175,7 @@ func TestIndirectPointers(t *testing.T) {
 | 
			
		||||
	assert.Equal(t, ToInt(z), 13)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestToTimeEE(t *testing.T) {
 | 
			
		||||
func TestToTime(t *testing.T) {
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		input  interface{}
 | 
			
		||||
		expect time.Time
 | 
			
		||||
@ -1285,3 +1287,148 @@ func TestToDurationE(t *testing.T) {
 | 
			
		||||
		assert.Equal(t, test.expect, v, errmsg)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestToTimeWithTimezones(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	est, err := time.LoadLocation("EST")
 | 
			
		||||
	require.NoError(t, err)
 | 
			
		||||
 | 
			
		||||
	irn, err := time.LoadLocation("Iran")
 | 
			
		||||
	require.NoError(t, err)
 | 
			
		||||
 | 
			
		||||
	require.NoError(t, err)
 | 
			
		||||
 | 
			
		||||
	// Test same local time in different timezones
 | 
			
		||||
	utc2016 := time.Date(2016, time.January, 1, 3, 1, 0, 0, time.UTC)
 | 
			
		||||
	est2016 := time.Date(2016, time.January, 1, 3, 1, 0, 0, est)
 | 
			
		||||
	irn2016 := time.Date(2016, time.January, 1, 3, 1, 0, 0, irn)
 | 
			
		||||
	loc2016 := time.Date(2016, time.January, 1, 3, 1, 0, 0, time.Local)
 | 
			
		||||
 | 
			
		||||
	for i, format := range timeFormats {
 | 
			
		||||
		format := format
 | 
			
		||||
		if format.typ == timeFormatShort {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		nameBase := fmt.Sprintf("%d;timeFormatType=%d;%s", i, format.typ, format.format)
 | 
			
		||||
 | 
			
		||||
		t.Run(path.Join(nameBase), func(t *testing.T) {
 | 
			
		||||
			est2016str := est2016.Format(format.format)
 | 
			
		||||
			loc2016str := loc2016.Format(format.format)
 | 
			
		||||
 | 
			
		||||
			t.Run("without default location", func(t *testing.T) {
 | 
			
		||||
				assert := require.New(t)
 | 
			
		||||
				converted, err := ToTimeE(est2016str)
 | 
			
		||||
				assert.NoError(err)
 | 
			
		||||
				if format.hasNumericTimezone() {
 | 
			
		||||
					assertTimeEqual(t, est2016, converted)
 | 
			
		||||
					assertLocationEqual(t, est, converted.Location())
 | 
			
		||||
				} else {
 | 
			
		||||
					assertTimeEqual(t, utc2016, converted)
 | 
			
		||||
					assertLocationEqual(t, time.UTC, converted.Location())
 | 
			
		||||
				}
 | 
			
		||||
			})
 | 
			
		||||
 | 
			
		||||
			t.Run("local timezone without a default location", func(t *testing.T) {
 | 
			
		||||
				assert := require.New(t)
 | 
			
		||||
				converted, err := ToTimeE(loc2016str)
 | 
			
		||||
				assert.NoError(err)
 | 
			
		||||
				if format.hasAnyTimezone() {
 | 
			
		||||
					// Local timezone strings can be either named or numeric and
 | 
			
		||||
					// time.Parse connects the dots.
 | 
			
		||||
					assertTimeEqual(t, loc2016, converted)
 | 
			
		||||
					assertLocationEqual(t, time.Local, converted.Location())
 | 
			
		||||
				} else {
 | 
			
		||||
					assertTimeEqual(t, utc2016, converted)
 | 
			
		||||
					assertLocationEqual(t, time.UTC, converted.Location())
 | 
			
		||||
				}
 | 
			
		||||
			})
 | 
			
		||||
 | 
			
		||||
			t.Run("nil default location", func(t *testing.T) {
 | 
			
		||||
				assert := require.New(t)
 | 
			
		||||
 | 
			
		||||
				converted, err := ToTimeInDefaultLocationE(est2016str, nil)
 | 
			
		||||
				assert.NoError(err)
 | 
			
		||||
				if format.hasNumericTimezone() {
 | 
			
		||||
					assertTimeEqual(t, est2016, converted)
 | 
			
		||||
					assertLocationEqual(t, est, converted.Location())
 | 
			
		||||
				} else {
 | 
			
		||||
					assertTimeEqual(t, utc2016, converted)
 | 
			
		||||
					assertLocationEqual(t, time.UTC, converted.Location())
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
			})
 | 
			
		||||
 | 
			
		||||
			t.Run("default location not UTC", func(t *testing.T) {
 | 
			
		||||
				assert := require.New(t)
 | 
			
		||||
 | 
			
		||||
				converted, err := ToTimeInDefaultLocationE(est2016str, irn)
 | 
			
		||||
				assert.NoError(err)
 | 
			
		||||
				if format.hasNumericTimezone() {
 | 
			
		||||
					assertTimeEqual(t, est2016, converted)
 | 
			
		||||
					assertLocationEqual(t, est, converted.Location())
 | 
			
		||||
				} else {
 | 
			
		||||
					assertTimeEqual(t, irn2016, converted)
 | 
			
		||||
					assertLocationEqual(t, irn, converted.Location())
 | 
			
		||||
				}
 | 
			
		||||
			})
 | 
			
		||||
 | 
			
		||||
			t.Run("time in the local timezone default location not UTC", func(t *testing.T) {
 | 
			
		||||
				assert := require.New(t)
 | 
			
		||||
 | 
			
		||||
				converted, err := ToTimeInDefaultLocationE(loc2016str, irn)
 | 
			
		||||
				assert.NoError(err)
 | 
			
		||||
 | 
			
		||||
				if format.hasNumericTimezone() {
 | 
			
		||||
					assertTimeEqual(t, loc2016, converted)
 | 
			
		||||
					assertLocationEqual(t, time.Local, converted.Location())
 | 
			
		||||
				} else {
 | 
			
		||||
					assertTimeEqual(t, irn2016, converted)
 | 
			
		||||
					assertLocationEqual(t, irn, converted.Location())
 | 
			
		||||
				}
 | 
			
		||||
			})
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func assertTimeEqual(t *testing.T, expected, actual time.Time) {
 | 
			
		||||
	t.Helper()
 | 
			
		||||
	require.True(t, expected.Equal(actual), fmt.Sprintf("expected\n%s\ngot\n%s", expected, actual))
 | 
			
		||||
	format := "2006-01-02 15:04:05.999999999 -0700"
 | 
			
		||||
	require.Equal(t, expected.Format(format), actual.Format(format))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func assertLocationEqual(t *testing.T, expected, actual *time.Location) {
 | 
			
		||||
	t.Helper()
 | 
			
		||||
	require.True(t, locationEqual(expected, actual), fmt.Sprintf("Expected location '%s', got '%s'", expected, actual))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func locationEqual(a, b *time.Location) bool {
 | 
			
		||||
	// A note about comparing time.Locations:
 | 
			
		||||
	//   - can't only compare pointers
 | 
			
		||||
	//   - can't compare loc.String() because locations with the same
 | 
			
		||||
	//     name can have different offsets
 | 
			
		||||
	//   - can't use reflect.DeepEqual because time.Location has internal
 | 
			
		||||
	//     caches
 | 
			
		||||
 | 
			
		||||
	if a == b {
 | 
			
		||||
		return true
 | 
			
		||||
	} else if a == nil || b == nil {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Check if they're equal by parsing times with a format that doesn't
 | 
			
		||||
	// include a timezone, which will interpret it as being a local time in
 | 
			
		||||
	// the given zone, and comparing the resulting local times.
 | 
			
		||||
	tA, err := time.ParseInLocation("2006-01-02", "2016-01-01", a)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	tB, err := time.ParseInLocation("2006-01-02", "2016-01-01", b)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return tA.Equal(tB)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										121
									
								
								caste.go
									
									
									
									
									
								
							
							
						
						
									
										121
									
								
								caste.go
									
									
									
									
									
								
							@ -20,13 +20,20 @@ var errNegativeNotAllowed = errors.New("unable to cast negative value")
 | 
			
		||||
 | 
			
		||||
// ToTimeE casts an interface to a time.Time type.
 | 
			
		||||
func ToTimeE(i interface{}) (tim time.Time, err error) {
 | 
			
		||||
	return ToTimeInDefaultLocationE(i, nil)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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 ToTimeInDefaultLocationE(i interface{}, location *time.Location) (tim time.Time, err error) {
 | 
			
		||||
	i = indirect(i)
 | 
			
		||||
 | 
			
		||||
	switch v := i.(type) {
 | 
			
		||||
	case time.Time:
 | 
			
		||||
		return v, nil
 | 
			
		||||
	case string:
 | 
			
		||||
		return StringToDate(v)
 | 
			
		||||
		return StringToDateInDefaultLocation(v, location)
 | 
			
		||||
	case int:
 | 
			
		||||
		return time.Unix(int64(v), 0), nil
 | 
			
		||||
	case int64:
 | 
			
		||||
@ -1204,43 +1211,97 @@ func ToDurationSliceE(i interface{}) ([]time.Duration, error) {
 | 
			
		||||
// predefined list of formats.  If no suitable format is found, an error is
 | 
			
		||||
// returned.
 | 
			
		||||
func StringToDate(s string) (time.Time, error) {
 | 
			
		||||
	return parseDateWith(s, []string{
 | 
			
		||||
		time.RFC3339,
 | 
			
		||||
		"2006-01-02T15:04:05", // iso8601 without timezone
 | 
			
		||||
		time.RFC1123Z,
 | 
			
		||||
		time.RFC1123,
 | 
			
		||||
		time.RFC822Z,
 | 
			
		||||
		time.RFC822,
 | 
			
		||||
		time.RFC850,
 | 
			
		||||
		time.ANSIC,
 | 
			
		||||
		time.UnixDate,
 | 
			
		||||
		time.RubyDate,
 | 
			
		||||
		"2006-01-02 15:04:05.999999999 -0700 MST", // Time.String()
 | 
			
		||||
		"2006-01-02",
 | 
			
		||||
		"02 Jan 2006",
 | 
			
		||||
		"2006-01-02T15:04:05-0700", // RFC3339 without timezone hh:mm colon
 | 
			
		||||
		"2006-01-02 15:04:05 -07:00",
 | 
			
		||||
		"2006-01-02 15:04:05 -0700",
 | 
			
		||||
		"2006-01-02 15:04:05Z07:00", // RFC3339 without T
 | 
			
		||||
		"2006-01-02 15:04:05Z0700",  // RFC3339 without T or timezone hh:mm colon
 | 
			
		||||
		"2006-01-02 15:04:05",
 | 
			
		||||
		time.Kitchen,
 | 
			
		||||
		time.Stamp,
 | 
			
		||||
		time.StampMilli,
 | 
			
		||||
		time.StampMicro,
 | 
			
		||||
		time.StampNano,
 | 
			
		||||
	})
 | 
			
		||||
	return parseDateWith(s, nil, timeFormats)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// StringToDateInDefaultLocation to parse a string into a time.Time type using a
 | 
			
		||||
// predefined list of formats, 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 StringToDateInDefaultLocation(s string, location *time.Location) (time.Time, error) {
 | 
			
		||||
	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)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
func parseDateWith(s string, dates []string) (d time.Time, e error) {
 | 
			
		||||
	for _, dateType := range dates {
 | 
			
		||||
		if d, e = time.Parse(dateType, s); e == nil {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return d, fmt.Errorf("unable to parse date: %s", s)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type timeFormatType int
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	timeFormatShort timeFormatType = iota // time or date only, no timezone
 | 
			
		||||
	timeFormatNoTimezone
 | 
			
		||||
 | 
			
		||||
	// All below have some kind of timezone information, a name and/or offset.
 | 
			
		||||
	timeFormatNamedTimezone
 | 
			
		||||
 | 
			
		||||
	// All below have what we consider to be solid timezone information.
 | 
			
		||||
	timeFormatNumericAndNamedTimezone
 | 
			
		||||
	timeFormatNumericTimezone
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type timeFormat struct {
 | 
			
		||||
	format string
 | 
			
		||||
	typ    timeFormatType
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (f timeFormat) hasNumericTimezone() bool {
 | 
			
		||||
	return f.typ >= timeFormatNumericAndNamedTimezone
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (f timeFormat) hasAnyTimezone() bool {
 | 
			
		||||
	return f.typ >= timeFormatNamedTimezone
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	timeFormats = []timeFormat{
 | 
			
		||||
		{time.RFC3339, timeFormatNumericTimezone},
 | 
			
		||||
		{"2006-01-02T15:04:05", timeFormatNoTimezone}, // iso8601 without timezone
 | 
			
		||||
		{time.RFC1123Z, timeFormatNumericTimezone},
 | 
			
		||||
		{time.RFC1123, timeFormatNamedTimezone},
 | 
			
		||||
		{time.RFC822Z, timeFormatNumericTimezone},
 | 
			
		||||
		{time.RFC822, timeFormatNamedTimezone},
 | 
			
		||||
		{time.RFC850, timeFormatNamedTimezone},
 | 
			
		||||
		{"2006-01-02 15:04:05.999999999 -0700 MST", timeFormatNumericAndNamedTimezone}, // Time.String()
 | 
			
		||||
		{"2006-01-02T15:04:05-0700", timeFormatNumericTimezone},                        // RFC3339 without 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},
 | 
			
		||||
		{time.ANSIC, timeFormatNoTimezone},
 | 
			
		||||
		// Must try RubyDate before UnixDate, see:
 | 
			
		||||
		// https://github.com/golang/go/issues/32358
 | 
			
		||||
		{time.RubyDate, timeFormatNumericTimezone},
 | 
			
		||||
		{time.UnixDate, timeFormatNamedTimezone},
 | 
			
		||||
		{"2006-01-02 15:04:05Z07:00", timeFormatNumericTimezone},
 | 
			
		||||
		{"2006-01-02", timeFormatShort},
 | 
			
		||||
		{"02 Jan 2006", timeFormatShort},
 | 
			
		||||
		{"2006-01-02 15:04:05 -07:00", timeFormatNumericTimezone},
 | 
			
		||||
		{"2006-01-02 15:04:05 -0700", timeFormatNumericTimezone},
 | 
			
		||||
		{time.Kitchen, timeFormatShort},
 | 
			
		||||
		{time.Stamp, timeFormatShort},
 | 
			
		||||
		{time.StampMilli, timeFormatShort},
 | 
			
		||||
		{time.StampMicro, timeFormatShort},
 | 
			
		||||
		{time.StampNano, timeFormatShort},
 | 
			
		||||
	}
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// jsonStringToObject attempts to unmarshall a string as JSON into
 | 
			
		||||
// the object passed as pointer.
 | 
			
		||||
func jsonStringToObject(s string, v interface{}) error {
 | 
			
		||||
 | 
			
		||||
		Ładowanie…
	
		Reference in New Issue
	
	Block a user