Repository: gin-gonic/gin
Pull request #1980 (2019-07-06 08:36:22)

binding: support unix time
ref:#1979

```golang
package main

import (
        "fmt"
        "github.com/gin-gonic/gin"
        "time"
)

type shareTime struct {
        CreateTime time.Time `form:"createTime" time_format:"unixNano"`
        UnixTime   time.Time `form:"unixTime" time_format:"unix"`
}

func main() {
        r := gin.Default()
        unix := r.Group("/unix")

        testCT := time.Date(2019, 7, 6, 16, 0, 33, 123, time.Local)
        fmt.Printf("%d\n", testCT.UnixNano())

        testUT := time.Date(2019, 7, 6, 16, 0, 33, 0, time.Local)
        fmt.Printf("%d\n", testUT.Unix())

        unix.GET("/nano", func(c *gin.Context) {
                s := shareTime{}

                c.ShouldBindQuery(&s)

                if !testCT.Equal(s.CreateTime) {
                        c.String(500, "want %d got %d", testCT.UnixNano(), s.CreateTime)
                        return
                }

                c.JSON(200, s)
        })

        unix.GET("/sec", func(c *gin.Context) {
                s := shareTime{}

                c.ShouldBindQuery(&s)

                if !testUT.Equal(s.UnixTime) {
                        c.String(500, "want %d got %d", testCT.Unix(), s.UnixTime)
                        return
                }

                c.JSON(200, s)

        })

        r.Run()
}

// client
//
// curl -X GET "127.0.0.1:8080/unix/sec?unixTime=1562400033"
//   {"CreateTime":"0001-01-01T00:00:00Z","UnixTime":"2019-07-06T16:00:33+08:00"}

// curl -X GET "127.0.0.1:8080/unix/nano?createTime=1562400033000000123"
//   {"CreateTime":"2019-07-06T16:00:33.000000123+08:00","UnixTime":"0001-01-01T00:00:00Z"}

codecov[bot]: # [Codecov](https://codecov.io/gh/gin-gonic/gin/pull/1980?src=pr&el=h1) Report
> Merging [#1980](https://codecov.io/gh/gin-gonic/gin/pull/1980?src=pr&el=desc) into [master](https://codecov.io/gh/gin-gonic/gin/commit/e602d524cccad90261e10bbb5ca41e9a81e467d4?src=pr&el=desc) will **decrease** coverage by `0.44%`.
> The diff coverage is `16.66%`.

[![Impacted file tree graph](https://codecov.io/gh/gin-gonic/gin/pull/1980/graphs/tree.svg?width=650&token=Qpqxhwery8&height=150&src=pr)](https://codecov.io/gh/gin-gonic/gin/pull/1980?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master    #1980      +/-   ##
==========================================
- Coverage   98.69%   98.25%   -0.45%     
==========================================
  Files          40       40              
  Lines        2218     2230      +12     
==========================================
+ Hits         2189     2191       +2     
- Misses         16       26      +10     
  Partials       13       13
```


| [Impacted Files](https://codecov.io/gh/gin-gonic/gin/pull/1980?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [binding/form\_mapping.go](https://codecov.io/gh/gin-gonic/gin/pull/1980/diff?src=pr&el=tree#diff-YmluZGluZy9mb3JtX21hcHBpbmcuZ28=) | `95.26% <16.66%> (-4.74%)` | :arrow_down: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/gin-gonic/gin/pull/1980?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/gin-gonic/gin/pull/1980?src=pr&el=footer). Last update [e602d52...e29ae3a](https://codecov.io/gh/gin-gonic/gin/pull/1980?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

codecov[bot]: # [Codecov](https://codecov.io/gh/gin-gonic/gin/pull/1980?src=pr&el=h1) Report
> Merging [#1980](https://codecov.io/gh/gin-gonic/gin/pull/1980?src=pr&el=desc) into [master](https://codecov.io/gh/gin-gonic/gin/commit/e602d524cccad90261e10bbb5ca41e9a81e467d4?src=pr&el=desc) will **decrease** coverage by `0.44%`.
> The diff coverage is `16.66%`.

[![Impacted file tree graph](https://codecov.io/gh/gin-gonic/gin/pull/1980/graphs/tree.svg?width=650&token=Qpqxhwery8&height=150&src=pr)](https://codecov.io/gh/gin-gonic/gin/pull/1980?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master    #1980      +/-   ##
==========================================
- Coverage   98.69%   98.25%   -0.45%     
==========================================
  Files          40       40              
  Lines        2218     2230      +12     
==========================================
+ Hits         2189     2191       +2     
- Misses         16       26      +10     
  Partials       13       13
```


| [Impacted Files](https://codecov.io/gh/gin-gonic/gin/pull/1980?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [binding/form\_mapping.go](https://codecov.io/gh/gin-gonic/gin/pull/1980/diff?src=pr&el=tree#diff-YmluZGluZy9mb3JtX21hcHBpbmcuZ28=) | `95.26% <16.66%> (-4.74%)` | :arrow_down: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/gin-gonic/gin/pull/1980?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/gin-gonic/gin/pull/1980?src=pr&el=footer). Last update [e602d52...e29ae3a](https://codecov.io/gh/gin-gonic/gin/pull/1980?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/README.md b/README.md
index 8d7705d..2761259 100644
--- a/README.md
+++ b/README.md
@@ -846,9 +846,11 @@ import (
 )
 
 type Person struct {
-	Name     string    `form:"name"`
-	Address  string    `form:"address"`
-	Birthday time.Time `form:"birthday" time_format:"2006-01-02" time_utc:"1"`
+        Name       string    `form:"name"`
+        Address    string    `form:"address"`
+        Birthday   time.Time `form:"birthday" time_format:"2006-01-02" time_utc:"1"`
+        CreateTime time.Time `form:"createTime" time_format:"unixNano"`
+        UnixTime   time.Time `form:"unixTime" time_format:"unix"`
 }
 
 func main() {
@@ -862,11 +864,13 @@ func startPage(c *gin.Context) {
 	// If `GET`, only `Form` binding engine (`query`) used.
 	// If `POST`, first checks the `content-type` for `JSON` or `XML`, then uses `Form` (`form-data`).
 	// See more at https://github.com/gin-gonic/gin/blob/master/binding/binding.go#L48
-	if c.ShouldBind(&person) == nil {
-		log.Println(person.Name)
-		log.Println(person.Address)
-		log.Println(person.Birthday)
-	}
+        if c.ShouldBind(&person) == nil {
+                log.Println(person.Name)
+                log.Println(person.Address)
+                log.Println(person.Birthday)
+                log.Println(person.CreateTime)
+                log.Println(person.UnixTime)
+        }
 
 	c.String(200, "Success")
 }
@@ -874,7 +878,7 @@ func startPage(c *gin.Context) {
 
 Test it with:
 ```sh
-$ curl -X GET "localhost:8085/testing?name=appleboy&address=xyz&birthday=1992-03-15"
+$ curl -X GET "localhost:8085/testing?name=appleboy&address=xyz&birthday=1992-03-15&createTime=1562400033000000123&unixTime=1562400033"
 ```
 
 ### Bind Uri
diff --git a/binding/form_mapping.go b/binding/form_mapping.go
index ebf3b19..80b1d15 100644
--- a/binding/form_mapping.go
+++ b/binding/form_mapping.go
@@ -266,6 +266,24 @@ func setTimeField(val string, structField reflect.StructField, value reflect.Val
 		timeFormat = time.RFC3339
 	}
 
+	switch tf := strings.ToLower(timeFormat); tf {
+	case "unix", "unixnano":
+		tv, err := strconv.ParseInt(val, 10, 0)
+		if err != nil {
+			return err
+		}
+
+		d := time.Duration(1)
+		if tf == "unixnano" {
+			d = time.Second
+		}
+
+		t := time.Unix(tv/int64(d), tv%int64(d))
+		value.Set(reflect.ValueOf(t))
+		return nil
+
+	}
+
 	if val == "" {
 		value.Set(reflect.ValueOf(time.Time{}))
 		return nil


Test patch:
diff --git a/binding/binding_test.go b/binding/binding_test.go
index 827518f..806f3ac 100644
--- a/binding/binding_test.go
+++ b/binding/binding_test.go
@@ -65,8 +65,15 @@ type FooStructUseNumber struct {
 }
 
 type FooBarStructForTimeType struct {
-	TimeFoo time.Time `form:"time_foo" time_format:"2006-01-02" time_utc:"1" time_location:"Asia/Chongqing"`
-	TimeBar time.Time `form:"time_bar" time_format:"2006-01-02" time_utc:"1"`
+	TimeFoo    time.Time `form:"time_foo" time_format:"2006-01-02" time_utc:"1" time_location:"Asia/Chongqing"`
+	TimeBar    time.Time `form:"time_bar" time_format:"2006-01-02" time_utc:"1"`
+	CreateTime time.Time `form:"createTime" time_format:"unixNano"`
+	UnixTime   time.Time `form:"unixTime" time_format:"unix"`
+}
+
+type FooStructForTimeTypeNotUnixFormat struct {
+	CreateTime time.Time `form:"createTime" time_format:"unixNano"`
+	UnixTime   time.Time `form:"unixTime" time_format:"unix"`
 }
 
 type FooStructForTimeTypeNotFormat struct {
@@ -226,7 +233,10 @@ func TestBindingFormDefaultValue2(t *testing.T) {
 func TestBindingFormForTime(t *testing.T) {
 	testFormBindingForTime(t, "POST",
 		"/", "/",
-		"time_foo=2017-11-15&time_bar=", "bar2=foo")
+		"time_foo=2017-11-15&time_bar=&createTime=1562400033000000123&unixTime=1562400033", "bar2=foo")
+	testFormBindingForTimeNotUnixFormat(t, "POST",
+		"/", "/",
+		"time_foo=2017-11-15&createTime=bad&unixTime=bad", "bar2=foo")
 	testFormBindingForTimeNotFormat(t, "POST",
 		"/", "/",
 		"time_foo=2017-11-15", "bar2=foo")
@@ -240,8 +250,11 @@ func TestBindingFormForTime(t *testing.T) {
 
 func TestBindingFormForTime2(t *testing.T) {
 	testFormBindingForTime(t, "GET",
-		"/?time_foo=2017-11-15&time_bar=", "/?bar2=foo",
+		"/?time_foo=2017-11-15&time_bar=&createTime=1562400033000000123&unixTime=1562400033", "/?bar2=foo",
 		"", "")
+	testFormBindingForTimeNotUnixFormat(t, "POST",
+		"/", "/",
+		"time_foo=2017-11-15&createTime=bad&unixTime=bad", "bar2=foo")
 	testFormBindingForTimeNotFormat(t, "GET",
 		"/?time_foo=2017-11-15", "/?bar2=foo",
 		"", "")
@@ -849,6 +862,8 @@ func testFormBindingForTime(t *testing.T, method, path, badPath, body, badBody s
 	assert.Equal(t, "Asia/Chongqing", obj.TimeFoo.Location().String())
 	assert.Equal(t, int64(-62135596800), obj.TimeBar.Unix())
 	assert.Equal(t, "UTC", obj.TimeBar.Location().String())
+	assert.Equal(t, int64(1562400033000000123), obj.CreateTime.UnixNano())
+	assert.Equal(t, int64(1562400033), obj.UnixTime.Unix())
 
 	obj = FooBarStructForTimeType{}
 	req = requestWithBody(method, badPath, badBody)
@@ -856,6 +871,24 @@ func testFormBindingForTime(t *testing.T, method, path, badPath, body, badBody s
 	assert.Error(t, err)
 }
 
+func testFormBindingForTimeNotUnixFormat(t *testing.T, method, path, badPath, body, badBody string) {
+	b := Form
+	assert.Equal(t, "form", b.Name())
+
+	obj := FooStructForTimeTypeNotUnixFormat{}
+	req := requestWithBody(method, path, body)
+	if method == "POST" {
+		req.Header.Add("Content-Type", MIMEPOSTForm)
+	}
+	err := b.Bind(req, &obj)
+	assert.Error(t, err)
+
+	obj = FooStructForTimeTypeNotUnixFormat{}
+	req = requestWithBody(method, badPath, badBody)
+	err = JSON.Bind(req, &obj)
+	assert.Error(t, err)
+}
+
 func testFormBindingForTimeNotFormat(t *testing.T, method, path, badPath, body, badBody string) {
 	b := Form
 	assert.Equal(t, "form", b.Name())