TL;DR
Arbitrary date unmarshal support + easily set marshal date format for both json and bson. The code and examples can be found here: https://github.com/simplereach/timeutils.
Small example
|
|
The Standard Library
Go provide an extensive support for dates/time in the standard library with the package time
.
This allows to easily deal with dates, compare them or make operations on them as well as moving from a timezone to an other.
Example
|
|
Formating
Within the time.Time
object, there are easy ways to format the date:
|
|
Parsing
When it comes to parsing, once again, the standard library offers tools.
Parsing date string
|
|
“Parsing” timestamp
|
|
This is great, but what if we don't know what time format we are expecting? i.e. user input or 3rd part API.
A solution would be to iterate through the available time formats until we succeed, but this is often cumbersome and unreliable.
Approxidate
The git library has this Approxidate
component that parses arbitrary date format and there is a Golang binding so we can use it!
https://godoc.org/github.com/simplereach/timeutils#ParseDateString
This expects a string as input and will do everything it can to properly yield a time object.
Case of JSON Marshal/Unmarshal
Unmarshal
Let's start with the unmarshal. What if we don't want to parse the time manually and let json.Unmarshal
handle it? Let's try:
|
|
Magically, it works fine! This is great, isn't it? But wait, the specs require us to send the date as RFC1123, is this going to work? Let's try as well!
|
|
2009/11/10 23:00:00 parsing time ""Fri, 07 Aug 2015 11:35:06 UTC"" as ""2006-01-02T15:04:05Z07:00"": cannot parse "Fri, 07 Aug 2015 11:35:06 UTC"" as "2006"
Oups.
So it does not work, how can we work around this?
A solution would be to implement the json.Unmarshaler
interface and handle our own parsing format, but we'll get to this.
Marshal
Ok, we have our time object, and we want to send it as json. Nothing easier:
|
|
It works fine :) However, the client expects times as RFC1123, how can we set the format to json.Marhsal
?
A way to do so would be to implement the json.Marshaler
interface and handling our own formatting.
Custom Marshal/Unmarshal
In order to tell Go to use a custom method for json marshal/unmarshal, one needs to implement the json.Marshaler
and json.Unmarshaler
interfaces.
As we can't do that on imported type time.Time
, we need to create a custom type.
Custom type
In order to create a custom type in Go, we simply do:
|
|
However, doing so “hides” all members and methods so we can't do things like this:
|
|
Which is pretty annoying as our goal is simply to override the JSON behavior. We still want our full blown object. To do so, we'll use a struct with an anynomous member:
|
|
This way, we can access all the methods of the nested time object.
Unmarshal RFC1123
As we expect RFC1123, we need a custom parsing, so le'ts implement json.Unmarshaler
.
Let's take our first RFC1123 example and improve it:
|
|
And now it works! We have a json unmarshal that supports RFC1123 instead of RFC3339!
To implment the json.Unmarshaler
interface, we need to write the func (t *myTime) UnmarshalJSON(buf []byte) error
method.
This receives the json buffer and return an error. It is expected to set the parsed value to the receiver so it is important that the receiver is a pointer.
The first step, has we expect valid json is to trim down the "
from the string, then we call the time.Parse
and finally set the result to our object.
Marshal RFC1123
Instead of the default RFC3339, let's have json encode our time as RFC1123:
|
|
Same idea as unmarshal. Here we only dump data so we don't want the receiver to be a pointer and we make sure that we return valid json wrapped in "
.
Going further
Changing the time format is great, but what if we need to move around dates as a timestamp integer? Or as a nanosecond timestamp? Or if we expect arbitrary format?
What if we have a REST API that need to move date between json and bson?
The timeutils
library (https://github.com/simplereach/timeutils
) offers a Time
type that supports arbitrary time format via aproxidate
as well as Timestamp and nanosecond precision both for marshal/unmarshal in json and bson.