Commits

Miki Tebeka  committed 31bf3f2

initial import

  • Participants

Comments (0)

Files changed (6)

+README.html
+The MIT License (MIT)
+
+Copyright (c) 2013 Miki Tebeka <miki.tebeka@gmail.com>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+# Flexible JSON time Handling
+
+`time.Time` marshal to JSON only in one format (RFC3339Nano).
+`jtime.Time` embeds `time.Time` and lets the user set the format via
+Marshallers.
+
+# Example
+
+    package main
+
+    import (
+            "encoding/json"
+            "fmt"
+            "log"
+
+            "bitbucket.org/tebeka/jtime"
+    )
+
+    type T struct {
+            Created jtime.Time `json:"created"`
+    }
+
+    func main() {
+            jtime.SetMarshaler(&jtime.UnixMarshaler{})
+            data := []byte(`{"created":1382135725}`) // Oct 18, 2013
+            t := T{}
+            if err := json.Unmarshal(data, &t); err != nil {
+                    log.Fatalf("error umarshaling: %s\n", err)
+            }
+            fmt.Println(t.Created) // 2013-10-18 15:35:25 -0700 PDT
+    }
+
+# Contact
+
+[https://bitbucket.org/tebeka/jtime](https://bitbucket.org/tebeka/jtime)
+
+# License
+MIT (see LICENSE.txt)
+/* Package jtime provides flexible JSON time.Time marshing/unmarshaling
+
+How it works?
+
+Use jtime.Time in your struct, and before calling json.Marshal/json.Unmarashal
+set the marshaller using SetMarshaller.
+
+You can either write your own marshaller or use two of the available marshallers:
+* FormatMashaller uses time.Time format to marshal/unmarshal time as JSON strings
+* UnixMarshaler marshal time.Time to JSON integers (with or without msec)
+
+jtime.Time embeds time.Time so you can use all time.Time methods with it.
+
+Example:
+
+	package main
+
+	import (
+		"encoding/json"
+		"fmt"
+		"log"
+
+		"bitbucket.org/tebeka/jtime"
+	)
+
+	type T struct {
+		Created jtime.Time `json:"created"`
+	}
+
+	func main() {
+		jtime.SetMarshaler(&jtime.UnixMarshaler{})
+		data := []byte(`{"created":1382135725}`) // Oct 18, 2013
+		t := T{}
+		if err := json.Unmarshal(data, &t); err != nil {
+			log.Fatalf("error umarshaling: %s\n", err)
+		}
+		fmt.Println(t.Created) // 2013-10-18 15:35:25 -0700 PDT
+	}
+
+Caution: Changing marshaller in mid-flight is dangerous :)
+*/
+package jtime
+package jtime
+
+import (
+	"fmt"
+	"strconv"
+	"time"
+)
+
+const (
+	Version = "0.1.0"
+)
+
+// Time structure, embeds time.Time
+type Time struct {
+	time.Time
+}
+
+// Marshler interface for marshaling/umarshaling time
+type Marshaler interface {
+	Marshal(t Time) ([]byte, error)
+	Unmarshal(data []byte) (Time, error)
+}
+
+var marshaler Marshaler
+
+// SetMarshaler sets the current marshaller
+func SetMarshaler(m Marshaler) {
+	marshaler = m
+}
+
+// FormatMashaler uses time.Time format strings
+type FormatMashaler struct {
+	Format string
+}
+
+// Marshal will marshal to JSON string in Format
+func (fm *FormatMashaler) Marshal(t Time) ([]byte, error) {
+	return []byte(`"` + t.Format(fm.Format) + `"`), nil
+}
+
+// Unmarshal from JSON string in Format
+func (fm *FormatMashaler) Unmarshal(data []byte) (Time, error) {
+	if len(data) < 2 {
+		return Time{}, fmt.Errorf("data too short - %v", data)
+	}
+	data = data[1 : len(data)-1]
+	t, err := time.Parse(fm.Format, string(data))
+	if err != nil {
+		return Time{}, err
+	}
+	return Time{t}, err
+}
+
+// UnixMarshaler uses integers as format
+type UnixMarshaler struct {
+	MSec bool // Time in millseconds
+}
+
+// Marashal to JSON integer
+func (um *UnixMarshaler) Marshal(t Time) ([]byte, error) {
+	data := fmt.Sprintf("%d", t.Unix())
+	if um.MSec {
+		data = fmt.Sprintf("%s%03d", data, t.Nanosecond()/1000)
+	}
+
+	return []byte(data), nil
+}
+
+// Unmarshal from JSON integer
+func (um *UnixMarshaler) Unmarshal(data []byte) (Time, error) {
+	sec, err := strconv.ParseInt(string(data), 10, 64)
+	if err != nil {
+		return Time{}, err
+	}
+	nsec := int64(0)
+	if um.MSec {
+		tmp := sec
+		sec = sec / 1000
+		nsec = (tmp - sec) * 1000
+	}
+
+	return Time{time.Unix(sec, nsec)}, nil
+}
+
+func validJSONTime(t Time) bool {
+	if y := t.Year(); y < 0 || y >= 10000 {
+		return false
+	}
+	return true
+}
+
+// MarshalJSON implements the json.Marshaler interface.
+func (t Time) MarshalJSON() ([]byte, error) {
+	if !validJSONTime(t) {
+		return nil, fmt.Errorf("vtime.Time.MarshalJson: year outside of range [0,9999]")
+	}
+	return marshaler.Marshal(t)
+}
+
+// UnmarshalJSON implements the json.Unmarshaler interface.
+func (t *Time) UnmarshalJSON(data []byte) (err error) {
+	*t, err = marshaler.Unmarshal(data)
+	return
+}
+
+func init() {
+	// Default behaviour
+	SetMarshaler(&FormatMashaler{time.RFC3339Nano})
+}

File jtime_test.go

+package jtime
+
+import (
+	"encoding/json"
+	"fmt"
+	"testing"
+	"time"
+)
+
+const (
+	testYear = 2013
+)
+
+var testYearStr = fmt.Sprintf(`"%d"`, testYear)
+
+type TestStruct struct {
+	Created Time `json:"created"`
+}
+
+func testTime() Time {
+	tt, _ := time.ParseInLocation(`"2006"`, testYearStr, time.UTC)
+	return Time{tt}
+}
+
+func TestFormatMarshaller(t *testing.T) {
+	tm := testTime()
+	fm := &FormatMashaler{"2006"}
+	data, err := fm.Marshal(tm)
+	if err != nil {
+		t.Fatalf("can't marshal: %s", err)
+	}
+
+	if string(data) != testYearStr {
+		t.Fatalf("bad marshal: %s", string(data))
+	}
+
+	pt, err := fm.Unmarshal(data)
+	if err != nil {
+		t.Fatalf("can't unmarshal: %s", err)
+	}
+
+	if pt.Year() != testYear {
+		t.Fatalf("bad year: %d", pt.Year())
+	}
+}
+
+func checkUnmarshal(data []byte, t *testing.T) {
+	ts1 := TestStruct{}
+	err := json.Unmarshal(data, &ts1)
+	if err != nil {
+		t.Fatalf("can't unmarshal - %s", err)
+	}
+
+	if ts1.Created.In(time.UTC).Year() != testYear {
+		t.Fatalf("bad unmarshaled year - %d", ts1.Created.Year())
+	}
+}
+
+func TestJSON(t *testing.T) {
+	SetMarshaler(&FormatMashaler{"2006"})
+
+	ts := TestStruct{testTime()}
+	data, err := json.Marshal(ts)
+	if err != nil {
+		t.Fatalf("can't json.Marshal - %s", err)
+	}
+	if string(data) != `{"created":"2013"}` {
+		t.Fatalf("bad json encoding: %s", string(data))
+	}
+
+	checkUnmarshal(data, t)
+}
+
+func TestJSONUnix(t *testing.T) {
+	SetMarshaler(&UnixMarshaler{})
+	ts := TestStruct{testTime()}
+	data, err := json.Marshal(ts)
+	if err != nil {
+		t.Fatalf("can't json.Marshal - %s", err)
+	}
+	if string(data) != `{"created":1356998400}` {
+		t.Fatalf("bad JSONUnix marshal - %s", string(data))
+	}
+
+	checkUnmarshal(data, t)
+}
+
+func TestJSONUnixMSec(t *testing.T) {
+	SetMarshaler(&UnixMarshaler{true})
+	ts := TestStruct{testTime()}
+	data, err := json.Marshal(ts)
+	if err != nil {
+		t.Fatalf("can't json.Marshal - %s", err)
+	}
+	if string(data) != `{"created":1356998400000}` {
+		t.Fatalf("bad JSONUnix marshal - %s", string(data))
+	}
+
+	checkUnmarshal(data, t)
+}