More specific types for ObjectStorage response object fields (#74)

* more accurate types for objectstorage response object fields (e.g. ContentLength: string -> in64)

* containers unit tests for new field types

* more specific types for accounts headers fields

* update accounts unit tests

* download header unmarshal method and unit test

* object results unmarshal methods
diff --git a/openstack/objectstorage/v1/objects/results.go b/openstack/objectstorage/v1/objects/results.go
index 3cfe1f4..1390faa 100644
--- a/openstack/objectstorage/v1/objects/results.go
+++ b/openstack/objectstorage/v1/objects/results.go
@@ -1,9 +1,11 @@
 package objects
 
 import (
+	"encoding/json"
 	"fmt"
 	"io"
 	"io/ioutil"
+	"strconv"
 	"strings"
 
 	"github.com/gophercloud/gophercloud"
@@ -24,7 +26,7 @@
 	// LastModified is the RFC3339Milli time the object was last modified, represented
 	// as a string. For any given object (obj), this value may be parsed to a time.Time:
 	// lastModified, err := time.Parse(gophercloud.RFC3339Milli, obj.LastModified)
-	LastModified string `json:"last_modified"`
+	LastModified gophercloud.JSONRFC1123 `json:"last_modified"`
 
 	// Name is the unique name for the object.
 	Name string `json:"name"`
@@ -101,7 +103,7 @@
 	AcceptRanges       string                  `json:"Accept-Ranges"`
 	ContentDisposition string                  `json:"Content-Disposition"`
 	ContentEncoding    string                  `json:"Content-Encoding"`
-	ContentLength      string                  `json:"Content-Length"`
+	ContentLength      int64                   `json:"-"`
 	ContentType        string                  `json:"Content-Type"`
 	Date               gophercloud.JSONRFC1123 `json:"Date"`
 	DeleteAt           gophercloud.JSONUnix    `json:"X-Delete-At"`
@@ -112,6 +114,32 @@
 	TransID            string                  `json:"X-Trans-Id"`
 }
 
+func (h *DownloadHeader) UnmarshalJSON(b []byte) error {
+	type tmp DownloadHeader
+	var hTmp *struct {
+		tmp
+		ContentLength string `json:"Content-Length"`
+	}
+	err := json.Unmarshal(b, &hTmp)
+	if err != nil {
+		return err
+	}
+
+	*h = DownloadHeader(hTmp.tmp)
+
+	switch hTmp.ContentLength {
+	case "":
+		h.ContentLength = 0
+	default:
+		h.ContentLength, err = strconv.ParseInt(hTmp.ContentLength, 10, 64)
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
 // DownloadResult is a *http.Response that is returned from a call to the Download function.
 type DownloadResult struct {
 	gophercloud.HeaderResult
@@ -148,7 +176,7 @@
 type GetHeader struct {
 	ContentDisposition string                  `json:"Content-Disposition"`
 	ContentEncoding    string                  `json:"Content-Encoding"`
-	ContentLength      string                  `json:"Content-Length"`
+	ContentLength      int64                   `json:"Content-Length"`
 	ContentType        string                  `json:"Content-Type"`
 	Date               gophercloud.JSONRFC1123 `json:"Date"`
 	DeleteAt           gophercloud.JSONUnix    `json:"X-Delete-At"`
@@ -159,6 +187,32 @@
 	TransID            string                  `json:"X-Trans-Id"`
 }
 
+func (h *GetHeader) UnmarshalJSON(b []byte) error {
+	type tmp GetHeader
+	var hTmp *struct {
+		tmp
+		ContentLength string `json:"Content-Length"`
+	}
+	err := json.Unmarshal(b, &hTmp)
+	if err != nil {
+		return err
+	}
+
+	*h = GetHeader(hTmp.tmp)
+
+	switch hTmp.ContentLength {
+	case "":
+		h.ContentLength = 0
+	default:
+		h.ContentLength, err = strconv.ParseInt(hTmp.ContentLength, 10, 64)
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
 // GetResult is a *http.Response that is returned from a call to the Get function.
 type GetResult struct {
 	gophercloud.HeaderResult
@@ -190,7 +244,7 @@
 
 // CreateHeader represents the headers returned in the response from a Create request.
 type CreateHeader struct {
-	ContentLength string                  `json:"Content-Length"`
+	ContentLength int64                   `json:"Content-Length"`
 	ContentType   string                  `json:"Content-Type"`
 	Date          gophercloud.JSONRFC1123 `json:"Date"`
 	ETag          string                  `json:"Etag"`
@@ -198,6 +252,32 @@
 	TransID       string                  `json:"X-Trans-Id"`
 }
 
+func (h *CreateHeader) UnmarshalJSON(b []byte) error {
+	type tmp CreateHeader
+	var hTmp *struct {
+		tmp
+		ContentLength string `json:"Content-Length"`
+	}
+	err := json.Unmarshal(b, &hTmp)
+	if err != nil {
+		return err
+	}
+
+	*h = CreateHeader(hTmp.tmp)
+
+	switch hTmp.ContentLength {
+	case "":
+		h.ContentLength = 0
+	default:
+		h.ContentLength, err = strconv.ParseInt(hTmp.ContentLength, 10, 64)
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
 // CreateResult represents the result of a create operation.
 type CreateResult struct {
 	checksum string
@@ -217,12 +297,38 @@
 
 // UpdateHeader represents the headers returned in the response from a Update request.
 type UpdateHeader struct {
-	ContentLength string                  `json:"Content-Length"`
+	ContentLength int64                   `json:"Content-Length"`
 	ContentType   string                  `json:"Content-Type"`
 	Date          gophercloud.JSONRFC1123 `json:"Date"`
 	TransID       string                  `json:"X-Trans-Id"`
 }
 
+func (h *UpdateHeader) UnmarshalJSON(b []byte) error {
+	type tmp UpdateHeader
+	var hTmp *struct {
+		tmp
+		ContentLength string `json:"Content-Length"`
+	}
+	err := json.Unmarshal(b, &hTmp)
+	if err != nil {
+		return err
+	}
+
+	*h = UpdateHeader(hTmp.tmp)
+
+	switch hTmp.ContentLength {
+	case "":
+		h.ContentLength = 0
+	default:
+		h.ContentLength, err = strconv.ParseInt(hTmp.ContentLength, 10, 64)
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
 // UpdateResult represents the result of an update operation.
 type UpdateResult struct {
 	gophercloud.HeaderResult
@@ -238,12 +344,38 @@
 
 // DeleteHeader represents the headers returned in the response from a Delete request.
 type DeleteHeader struct {
-	ContentLength string                  `json:"Content-Length"`
+	ContentLength int64                   `json:"Content-Length"`
 	ContentType   string                  `json:"Content-Type"`
 	Date          gophercloud.JSONRFC1123 `json:"Date"`
 	TransID       string                  `json:"X-Trans-Id"`
 }
 
+func (h *DeleteHeader) UnmarshalJSON(b []byte) error {
+	type tmp DeleteHeader
+	var hTmp *struct {
+		tmp
+		ContentLength string `json:"Content-Length"`
+	}
+	err := json.Unmarshal(b, &hTmp)
+	if err != nil {
+		return err
+	}
+
+	*h = DeleteHeader(hTmp.tmp)
+
+	switch hTmp.ContentLength {
+	case "":
+		h.ContentLength = 0
+	default:
+		h.ContentLength, err = strconv.ParseInt(hTmp.ContentLength, 10, 64)
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
 // DeleteResult represents the result of a delete operation.
 type DeleteResult struct {
 	gophercloud.HeaderResult
@@ -269,6 +401,32 @@
 	TransID                string                  `json:"X-Trans-Id"`
 }
 
+func (h *CopyHeader) UnmarshalJSON(b []byte) error {
+	type tmp CopyHeader
+	var hTmp *struct {
+		tmp
+		ContentLength string `json:"Content-Length"`
+	}
+	err := json.Unmarshal(b, &hTmp)
+	if err != nil {
+		return err
+	}
+
+	*h = CopyHeader(hTmp.tmp)
+
+	switch hTmp.ContentLength {
+	case "":
+		h.ContentLength = 0
+	default:
+		h.ContentLength, err = strconv.ParseInt(hTmp.ContentLength, 10, 64)
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
 // CopyResult represents the result of a copy operation.
 type CopyResult struct {
 	gophercloud.HeaderResult
diff --git a/openstack/objectstorage/v1/objects/testing/fixtures.go b/openstack/objectstorage/v1/objects/testing/fixtures.go
index cf1d6c5..d3144cb 100644
--- a/openstack/objectstorage/v1/objects/testing/fixtures.go
+++ b/openstack/objectstorage/v1/objects/testing/fixtures.go
@@ -6,12 +6,18 @@
 	"io"
 	"net/http"
 	"testing"
+	"time"
 
+	"github.com/gophercloud/gophercloud"
 	"github.com/gophercloud/gophercloud/openstack/objectstorage/v1/objects"
 	th "github.com/gophercloud/gophercloud/testhelper"
 	fake "github.com/gophercloud/gophercloud/testhelper/client"
 )
 
+var (
+	loc, _ = time.LoadLocation("GMT")
+)
+
 // HandleDownloadObjectSuccessfully creates an HTTP handler at `/testContainer/testObject` on the test handler mux that
 // responds with a `Download` response.
 func HandleDownloadObjectSuccessfully(t *testing.T) {
@@ -19,6 +25,7 @@
 		th.TestMethod(t, r, "GET")
 		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
 		th.TestHeader(t, r, "Accept", "application/json")
+		w.Header().Set("Date", "Wed, 10 Nov 2009 23:00:00 GMT")
 		w.WriteHeader(http.StatusOK)
 		fmt.Fprintf(w, "Successful download with Gophercloud")
 	})
@@ -29,14 +36,14 @@
 var ExpectedListInfo = []objects.Object{
 	{
 		Hash:         "451e372e48e0f6b1114fa0724aa79fa1",
-		LastModified: "2009-11-10 23:00:00 +0000 UTC",
+		LastModified: gophercloud.JSONRFC1123(time.Date(2009, time.November, 10, 23, 0, 0, 0, loc)), //"2009-11-10 23:00:00 +0000 UTC"
 		Bytes:        14,
 		Name:         "goodbye",
 		ContentType:  "application/octet-stream",
 	},
 	{
 		Hash:         "451e372e48e0f6b1114fa0724aa79fa1",
-		LastModified: "2009-11-10 23:00:00 +0000 UTC",
+		LastModified: gophercloud.JSONRFC1123(time.Date(2009, time.November, 10, 23, 0, 0, 0, loc)),
 		Bytes:        14,
 		Name:         "hello",
 		ContentType:  "application/octet-stream",
@@ -63,14 +70,14 @@
 			fmt.Fprintf(w, `[
       {
         "hash": "451e372e48e0f6b1114fa0724aa79fa1",
-        "last_modified": "2009-11-10 23:00:00 +0000 UTC",
+        "last_modified": "Wed, 10 Nov 2009 23:00:00 GMT",
         "bytes": 14,
         "name": "goodbye",
         "content_type": "application/octet-stream"
       },
       {
         "hash": "451e372e48e0f6b1114fa0724aa79fa1",
-        "last_modified": "2009-11-10 23:00:00 +0000 UTC",
+        "last_modified": "Wed, 10 Nov 2009 23:00:00 GMT",
         "bytes": 14,
         "name": "hello",
         "content_type": "application/octet-stream"
diff --git a/openstack/objectstorage/v1/objects/testing/requests_test.go b/openstack/objectstorage/v1/objects/testing/requests_test.go
index 2b9306f..7762d08 100644
--- a/openstack/objectstorage/v1/objects/testing/requests_test.go
+++ b/openstack/objectstorage/v1/objects/testing/requests_test.go
@@ -5,7 +5,9 @@
 	"io"
 	"strings"
 	"testing"
+	"time"
 
+	"github.com/gophercloud/gophercloud"
 	"github.com/gophercloud/gophercloud/openstack/objectstorage/v1/objects"
 	"github.com/gophercloud/gophercloud/pagination"
 	th "github.com/gophercloud/gophercloud/testhelper"
@@ -37,6 +39,15 @@
 	bytes, err := response.ExtractContent()
 	th.AssertNoErr(t, err)
 	th.CheckEquals(t, "Successful download with Gophercloud", string(bytes))
+
+	expected := &objects.DownloadHeader{
+		ContentLength: 36,
+		ContentType:   "text/plain; charset=utf-8",
+		Date:          gophercloud.JSONRFC1123(time.Date(2009, time.November, 10, 23, 0, 0, 0, loc)),
+	}
+	actual, err := response.Extract()
+	th.AssertNoErr(t, err)
+	th.CheckDeepEquals(t, expected, actual)
 }
 
 func TestListObjectInfo(t *testing.T) {