-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2 from creack/feat/creack/jsontime
Add jsontime type for easy marshal/unmarshal json/bson
- Loading branch information
Showing
3 changed files
with
580 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
package timeutils_test | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"os" | ||
"time" | ||
|
||
"github.com/simplereach/timeutils" | ||
) | ||
|
||
func ExampleNewTime() { | ||
defer os.Setenv("TZ", os.Getenv("TZ")) | ||
os.Setenv("TZ", "UTC") | ||
|
||
std := time.Unix(1438883568, 790859087) | ||
enc := json.NewEncoder(os.Stdout) | ||
_ = enc.Encode(timeutils.NewTime(std, timeutils.TimestampNano)) | ||
_ = enc.Encode(timeutils.NewTime(std, timeutils.Timestamp)) | ||
_ = enc.Encode(timeutils.NewTime(std, timeutils.RFC3339)) | ||
_ = enc.Encode(timeutils.NewTime(std, timeutils.RFC3339Nano)) | ||
|
||
// output: | ||
// 1438883568790859087 | ||
// 1438883568 | ||
// "2015-08-06T17:52:48Z" | ||
// "2015-08-06T17:52:48.790859087Z" | ||
} | ||
|
||
func ExampleTime() { | ||
defer os.Setenv("TZ", os.Getenv("TZ")) | ||
os.Setenv("TZ", "UTC") | ||
|
||
var t timeutils.Time | ||
_ = json.Unmarshal([]byte(`1438883568`), &t) | ||
fmt.Println(t.UTC()) | ||
t = timeutils.Time{} | ||
_ = json.Unmarshal([]byte(`1438883568790859087`), &t) | ||
fmt.Println(t.UTC()) | ||
t = timeutils.Time{} | ||
_ = json.Unmarshal([]byte(`"2015-08-06T17:52:48Z"`), &t) | ||
fmt.Println(t.UTC()) | ||
t = timeutils.Time{} | ||
_ = json.Unmarshal([]byte(`"09:51:20.939152pm 2014-31-12"`), &t) | ||
fmt.Println(t.UTC()) | ||
|
||
// output: | ||
// 2015-08-06 17:52:48 +0000 UTC | ||
// 2015-08-06 17:52:48.790859087 +0000 UTC | ||
// 2015-08-06 17:52:48 +0000 UTC | ||
// 2014-12-31 21:51:20.939152 +0000 UTC | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,196 @@ | ||
package timeutils | ||
|
||
import ( | ||
"encoding/json" | ||
"errors" | ||
"strconv" | ||
"strings" | ||
"time" | ||
|
||
"gopkg.in/mgo.v2/bson" | ||
) | ||
|
||
// Format enum type. | ||
type Format int32 | ||
|
||
// Format enum values. | ||
const ( | ||
Timestamp Format = iota | ||
TimestampNano | ||
ANSIC | ||
UnixDate | ||
RubyDate | ||
RFC822 | ||
RFC822Z | ||
RFC850 | ||
RFC1123 | ||
RFC1123Z | ||
RFC3339 | ||
RFC3339Nano | ||
Kitchen | ||
) | ||
|
||
// Common errors. | ||
var ( | ||
ErrInvalidFormat = errors.New("invalid format") | ||
) | ||
|
||
// Time wraps time.Time overriddin the json marshal/unmarshal to pass | ||
// timestamp as integer | ||
type Time struct { | ||
time.Time `bson:",inline"` | ||
format Format | ||
} | ||
|
||
// NewTime create a new Time object with the given format. | ||
func NewTime(t time.Time, format Format) Time { | ||
return Time{ | ||
Time: t, | ||
format: format, | ||
} | ||
} | ||
|
||
// FormatMode create a copy of the time object | ||
// and sets the format method to be used by | ||
// the marhsal functions. | ||
func (t Time) FormatMode(format Format) Time { | ||
t.format = format | ||
return t | ||
} | ||
|
||
func (t Time) formatTime(mode int) ([]byte, error) { | ||
var ret string | ||
|
||
switch t.format { | ||
case ANSIC: | ||
ret = t.Time.Format(time.ANSIC) | ||
case UnixDate: | ||
ret = t.Time.Format(time.UnixDate) | ||
case RubyDate: | ||
ret = t.Time.Format(time.RubyDate) | ||
case RFC822: | ||
ret = t.Time.Format(time.RFC822) | ||
case RFC822Z: | ||
ret = t.Time.Format(time.RFC822Z) | ||
case RFC850: | ||
ret = t.Time.Format(time.RFC850) | ||
case RFC1123: | ||
ret = t.Time.Format(time.RFC1123) | ||
case RFC1123Z: | ||
ret = t.Time.Format(time.RFC1123Z) | ||
case RFC3339: | ||
ret = t.Time.Format(time.RFC3339) | ||
case RFC3339Nano: | ||
ret = t.Time.Format(time.RFC3339Nano) | ||
case Kitchen: | ||
ret = t.Time.Format(time.Kitchen) | ||
case Timestamp: | ||
return []byte(strconv.FormatInt(t.Time.Unix(), 10)), nil | ||
case TimestampNano: | ||
return []byte(strconv.FormatInt(t.Time.UnixNano(), 10)), nil | ||
default: | ||
return nil, ErrInvalidFormat | ||
} | ||
switch mode { | ||
default: | ||
fallthrough | ||
case 0: // json | ||
return []byte(`"` + ret + `"`), nil | ||
case 1: // bson | ||
return []byte(ret), nil | ||
} | ||
} | ||
|
||
// MarshalJSON implements json.Marshaler interface. | ||
func (t Time) MarshalJSON() ([]byte, error) { | ||
if t.Time.IsZero() { | ||
return []byte("null"), nil | ||
} | ||
return t.formatTime(0) | ||
} | ||
|
||
// UnmarshalJSON implements json.Unmarshaler inferface. | ||
func (t *Time) UnmarshalJSON(buf []byte) error { | ||
// Try to parse the timestamp integer | ||
ts, err := strconv.ParseInt(string(buf), 10, 64) | ||
if err == nil { | ||
if len(buf) == 19 { | ||
t.Time = time.Unix(ts/1e9, ts%1e9) | ||
} else { | ||
t.Time = time.Unix(ts, 0) | ||
} | ||
return nil | ||
} | ||
// Try the default unmarshal | ||
if err := json.Unmarshal(buf, &t.Time); err == nil { | ||
return nil | ||
} | ||
str := strings.Trim(string(buf), `"`) | ||
if str == "null" || str == "" { | ||
return nil | ||
} | ||
// Try to manually parse the data | ||
tt, err := ParseDateString(str) | ||
if err != nil { | ||
return err | ||
} | ||
t.Time = tt | ||
return nil | ||
} | ||
|
||
// GetBSON implements mgo/bson.Getter interface. | ||
func (t Time) GetBSON() (interface{}, error) { | ||
if t.Time.IsZero() { | ||
return nil, nil | ||
} | ||
return t.formatTime(1) | ||
} | ||
|
||
// SetBSON implements mgo/bson.Setter interface. | ||
func (t *Time) SetBSON(raw bson.Raw) error { | ||
// Try the default unmarshal | ||
if err := raw.Unmarshal(&t.Time); err == nil { | ||
return nil | ||
} | ||
|
||
// Try to pull the timestamp as an int | ||
var tsInt int64 | ||
if err := raw.Unmarshal(&tsInt); err == nil { | ||
if tsInt > 5e9 { | ||
t.Time = time.Unix(tsInt/1e9, tsInt%1e9) | ||
} else { | ||
t.Time = time.Unix(tsInt, 0) | ||
} | ||
} | ||
|
||
// Try to pull the timestamp as a string | ||
var tsStr string | ||
if err := raw.Unmarshal(&tsStr); err != nil { | ||
return err | ||
} | ||
if tsStr == "" { | ||
return nil | ||
} | ||
ts, err := strconv.ParseInt(tsStr, 10, 64) | ||
if err == nil { | ||
if len(tsStr) == 19 { | ||
t.Time = time.Unix(ts/1e9, ts%1e9) | ||
} else { | ||
t.Time = time.Unix(ts, 0) | ||
} | ||
return nil | ||
} | ||
|
||
// Try the json umarshal | ||
if err := json.Unmarshal([]byte(`"`+tsStr+`"`), &t.Time); err == nil { | ||
return nil | ||
} | ||
|
||
// Try to manually parse the data | ||
tt, err := ParseDateString(tsStr) | ||
if err != nil { | ||
return err | ||
} | ||
t.Time = tt | ||
return nil | ||
} |
Oops, something went wrong.