Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: spf13/cast
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v1.3.1
Choose a base ref
...
head repository: spf13/cast
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v1.4.1
Choose a head ref
  • 10 commits
  • 7 files changed
  • 5 contributors

Commits on Jul 10, 2020

  1. Copy the full SHA
    580a25a View commit details

Commits on Jul 23, 2020

  1. Convert error slice to string slice

    * feat: added test case for error slice to parse to string
    
    * feat: added handler to convert an error slice to string slice
    PacoDw authored Jul 23, 2020
    Copy the full SHA
    8d17101 View commit details

Commits on Jul 27, 2021

  1. travis: Bump Go versions

    bep committed Jul 27, 2021
    Copy the full SHA
    c3e59ce View commit details
  2. 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.
    heewa authored and bep committed Jul 27, 2021
    Copy the full SHA
    e4dda5f View commit details
  3. Adjust timezone logic

    bep committed Jul 27, 2021
    Copy the full SHA
    22b2b54 View commit details
  4. Create go.yml

    bep authored Jul 27, 2021
    Copy the full SHA
    57c98fb View commit details
  5. Remove .travis.yml

    Use GitHub actions instead.
    bep committed Jul 27, 2021
    Copy the full SHA
    a9f3cbf View commit details
  6. Update README.md

    bep authored Jul 27, 2021
    Copy the full SHA
    02aebd9 View commit details
  7. github: Also run tests on Windows

    bep committed Jul 27, 2021
    Copy the full SHA
    3f42935 View commit details

Commits on Aug 15, 2021

  1. Copy the full SHA
    8807572 View commit details
Showing with 357 additions and 48 deletions.
  1. +28 −0 .github/workflows/go.yml
  2. +0 −16 .travis.yml
  3. +1 −1 README.md
  4. +5 −0 cast.go
  5. +178 −1 cast_test.go
  6. +118 −30 caste.go
  7. +27 −0 timeformattype_string.go
28 changes: 28 additions & 0 deletions .github/workflows/go.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
name: Go

on:
push:
branches: [ master ]
pull_request:
branches: [ master ]

jobs:
build:
strategy:
matrix:
go-version: [1.16.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 ./...
16 changes: 0 additions & 16 deletions .travis.yml

This file was deleted.

2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
cast
====
[![GoDoc](https://godoc.org/github.com/spf13/cast?status.svg)](https://godoc.org/github.com/spf13/cast)
[![Build Status](https://api.travis-ci.org/spf13/cast.svg?branch=master)](https://travis-ci.org/spf13/cast)
[![Build Status](https://github.com/spf13/cast/actions/workflows/go.yml/badge.svg)](https://github.com/spf13/cast/actions/workflows/go.yml)
[![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
5 changes: 5 additions & 0 deletions cast.go
Original file line number Diff line number Diff line change
@@ -20,6 +20,11 @@ func ToTime(i interface{}) time.Time {
return v
}

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)
179 changes: 178 additions & 1 deletion cast_test.go
Original file line number Diff line number Diff line change
@@ -6,12 +6,15 @@
package cast

import (
"errors"
"fmt"
"html/template"
"path"
"testing"
"time"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestToUintE(t *testing.T) {
@@ -1048,9 +1051,16 @@ func TestToStringSliceE(t *testing.T) {
expect []string
iserr bool
}{
{[]int{1, 2}, []string{"1", "2"}, false},
{[]int8{int8(1), int8(2)}, []string{"1", "2"}, false},
{[]int32{int32(1), int32(2)}, []string{"1", "2"}, false},
{[]int64{int64(1), int64(2)}, []string{"1", "2"}, false},
{[]float32{float32(1.01), float32(2.01)}, []string{"1.01", "2.01"}, false},
{[]float64{float64(1.01), float64(2.01)}, []string{"1.01", "2.01"}, false},
{[]string{"a", "b"}, []string{"a", "b"}, false},
{[]interface{}{1, 3}, []string{"1", "3"}, false},
{interface{}(1), []string{"1"}, false},
{[]error{errors.New("a"), errors.New("b")}, []string{"a", "b"}, false},
// errors
{nil, nil, true},
{testing.T{}, nil, true},
@@ -1173,7 +1183,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
@@ -1201,6 +1211,8 @@ func TestToTimeEE(t *testing.T) {
{"2016-03-06 15:28:01", time.Date(2016, 3, 6, 15, 28, 1, 0, time.UTC), false},
{"2016-03-06 15:28:01 -0000", time.Date(2016, 3, 6, 15, 28, 1, 0, time.UTC), false},
{"2016-03-06 15:28:01 -00:00", time.Date(2016, 3, 6, 15, 28, 1, 0, time.UTC), false},
{"2016-03-06 15:28:01 +0900", time.Date(2016, 3, 6, 6, 28, 1, 0, time.UTC), false},
{"2016-03-06 15:28:01 +09:00", time.Date(2016, 3, 6, 6, 28, 1, 0, time.UTC), false},
{"2006-01-02", time.Date(2006, 1, 2, 0, 0, 0, 0, time.UTC), false},
{"02 Jan 2006", time.Date(2006, 1, 2, 0, 0, 0, 0, time.UTC), false},
{1472574600, time.Date(2016, 8, 30, 16, 30, 0, 0, time.UTC), false},
@@ -1285,3 +1297,168 @@ 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)

swd, err := time.LoadLocation("Europe/Stockholm")
require.NoError(t, err)

// Test same local time in different timezones
utc2016 := time.Date(2016, time.January, 1, 0, 0, 0, 0, time.UTC)
est2016 := time.Date(2016, time.January, 1, 0, 0, 0, 0, est)
irn2016 := time.Date(2016, time.January, 1, 0, 0, 0, 0, irn)
swd2016 := time.Date(2016, time.January, 1, 0, 0, 0, 0, swd)
loc2016 := time.Date(2016, time.January, 1, 0, 0, 0, 0, time.Local)

for i, format := range timeFormats {
format := format
if format.typ == timeFormatTimeOnly {
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)
swd2016str := swd2016.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.hasTimezone() {
// Converting inputs with a timezone should preserve it
assertTimeEqual(t, est2016, converted)
assertLocationEqual(t, est, converted.Location())
} else {
// Converting inputs without a timezone should be interpreted
// as a local time in UTC.
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(swd2016str)
assert.NoError(err)
if format.hasTimezone() {
// Converting inputs with a timezone should preserve it
assertTimeEqual(t, swd2016, converted)
assertLocationEqual(t, swd, converted.Location())
} else {
// Converting inputs without a timezone should be interpreted
// as a local time in UTC.
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.hasTimezone() {
// Converting inputs with a timezone should preserve it
assertTimeEqual(t, est2016, converted)
assertLocationEqual(t, est, converted.Location())
} else {
// Converting inputs without a timezone should be interpreted
// as a local time in the local timezone.
assertTimeEqual(t, loc2016, converted)
assertLocationEqual(t, time.Local, 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.hasTimezone() {
// Converting inputs with a timezone should preserve it
assertTimeEqual(t, est2016, converted)
assertLocationEqual(t, est, converted.Location())
} else {
// Converting inputs without a timezone should be interpreted
// as a local time in the given location.
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(swd2016str, irn)
assert.NoError(err)
if format.hasTimezone() {
// Converting inputs with a timezone should preserve it
assertTimeEqual(t, swd2016, converted)
assertLocationEqual(t, swd, converted.Location())
} else {
// Converting inputs without a timezone should be interpreted
// as a local time in the given location.
assertTimeEqual(t, irn2016, converted)
assertLocationEqual(t, irn, converted.Location())
}

})

})

}
}

func assertTimeEqual(t *testing.T, expected, actual time.Time) {
t.Helper()
// Compare the dates using a numeric zone as there are cases where
// time.Parse will assign a dummy location.
// TODO(bep)
//require.Equal(t, expected, actual)
require.Equal(t, expected.Format(time.RFC1123Z), actual.Format(time.RFC1123Z))
}

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 comparring 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)
}
Loading