time.Parse date string with sub-second times & Unmarshal json time.Time

5,307 views
Skip to first unread message

Hokapoka

unread,
Dec 15, 2010, 10:36:43 AM12/15/10
to golang-dev
Hello

I'm working with some json from Google APIs that contain the follow
date format :

"2010-12-15T03:56:26.650Z"

To convert this to type Time I have to remove the ".XXXZ" sub-seconds
suffix before parsing using :

time.Parse( "2006-01-02T15:04:05", "2010-12-15T03:56:26")

Is there any way, or plans to include, a means to tell the parse to
ignore parts of the string when parsing.

If I understand correctly the go Time package doesn't fully support
nanoseconds or sub-second time values, but it would be great if it
were able to pass the Time strings without having to manipulate them
prior to Parsing.

The other aspect of this time format is when using Unmarshalling the
json data to a type that is time.Time it errors.

So currently after the json data has been unmarshalled I then re-parse
the date string into time.Time types.

Are there any of json.Unmarshal support these, or any time formats?

Many thanks.


Russ Cox

unread,
Dec 15, 2010, 10:57:38 AM12/15/10
to Hokapoka, golang-dev
> "2010-12-15T03:56:26.650Z"
>
> To convert this to type Time I have to remove the ".XXXZ" sub-seconds
> suffix before parsing using :
>
> time.Parse( "2006-01-02T15:04:05", "2010-12-15T03:56:26")
>
> Is there any way, or plans to include, a means to tell the parse to
> ignore parts of the string when parsing.

There isn't.

> The other aspect of this time format is when using Unmarshalling the
> json data to a type that is time.Time it errors.
>
> So currently after the json data has been unmarshalled I then re-parse
> the date string into time.Time types.
>
> Are there any of json.Unmarshal support these, or any time formats?

If you define your own time type and use that in the structures,
then you can give it an UnmarshalJSON method that can do
its own unmarshaling. That might be a good place to put the
stripping of .650 too.

See http://golang.org/pkg/json/#Unmarshaler

Russ

Hokapoka

unread,
Dec 15, 2010, 11:04:27 AM12/15/10
to golang-dev
> If you define your own time type and use that in the structures,
> then you can give it an UnmarshalJSON method that can do
> its own unmarshaling.  That might be a good place to put the
> stripping of .650 too.
>
> See http://golang.org/pkg/json/#Unmarshaler
>
> Russ

Great, many thanks Russ.

roger peppe

unread,
Dec 15, 2010, 11:07:17 AM12/15/10
to r...@golang.org, Hokapoka, golang-dev
On 15 December 2010 15:57, Russ Cox <r...@golang.org> wrote:
> If you define your own time type and use that in the structures,
> then you can give it an UnmarshalJSON method that can do
> its own unmarshaling.  That might be a good place to put the
> stripping of .650 too.
>
> See http://golang.org/pkg/json/#Unmarshaler

here's an example of that.
it was an interesting exercise - i wonder if there might be a place
for a couple of specialised functions to marshal and unmarshal JSON
strings, to avoid creating a bytes.Buffer and a json.Encoder/Decoder
each time. perhaps there's an easier way that i'm missing.

i thought it might be reasonable too to have a Nanoseconds
field in time.Time and a way of parsing/formatting sub-second time (999?)

package main
import (
"json"
"time"
"os"
"bytes"
"fmt"
)

const Fmt = "2006-01-02T15:04:05"
type jTime time.Time
func (jt *jTime) UnmarshalJSON(data []byte) os.Error {
b := bytes.NewBuffer(data)
dec := json.NewDecoder(b)
var s string
if err := dec.Decode(&s); err != nil {
return err
}
t, err := time.Parse(Fmt, s)
if err != nil {
return err
}
*jt = (jTime)(*t)
return nil
}

func (jt jTime) MarshalJSON() ([]byte, os.Error) {
var b bytes.Buffer
enc := json.NewEncoder(&b)
s := (*time.Time)(&jt).Format(Fmt)
enc.Encode(s)
return b.Bytes(), nil
}

type Foo struct {
T *jTime
I int
}

func main() {
var b bytes.Buffer
enc := json.NewEncoder(&b)

foo1 := Foo{(*jTime)(time.LocalTime()), 99}
if err := enc.Encode(foo1); err != nil {
fmt.Printf("err: %v\n", err)
}else{
fmt.Printf("foo1: %s\n", b.Bytes())
}

var foo2 Foo
dec := json.NewDecoder(&b)
if err := dec.Decode(&foo2); err != nil {
fmt.Printf("decode err: %v\n", err)
}else{
fmt.Printf("foo2: %v\n", foo2)
}
}

Russ Cox

unread,
Dec 15, 2010, 11:11:17 AM12/15/10
to roger peppe, Hokapoka, golang-dev
I think you can use json.Marshal and json.Unmarshal
directly to avoid setting up encoders and decoders.

Russ

roger peppe

unread,
Dec 15, 2010, 11:18:19 AM12/15/10
to r...@golang.org, Hokapoka, golang-dev
On 15 December 2010 16:11, Russ Cox <r...@golang.org> wrote:
> I think you can use json.Marshal and json.Unmarshal
> directly to avoid setting up encoders and decoders.

how did i miss that?!

const Fmt = "2006-01-02T15:04:05"
type jTime time.Time
func (jt *jTime) UnmarshalJSON(data []byte) os.Error {

var s string
if err := json.Unmarshal(data, &s); err != nil {


return err
}
t, err := time.Parse(Fmt, s)
if err != nil {
return err
}
*jt = (jTime)(*t)
return nil
}

func (jt jTime) MarshalJSON() ([]byte, os.Error) {

return json.Marshal((*time.Time)(&jt).Format(Fmt))
}

Hokapoka

unread,
Dec 15, 2010, 12:07:47 PM12/15/10
to golang-dev

That's great, thanks for the help Roger.

Here's how I'm Parsing the string :

package main
import (
"json"
"time"
"os"
"fmt"
"strings"
)

const Fmt = "2006-01-02T15:04:05"
type jTime time.Time
func (jt *jTime) UnmarshalJSON(data []byte) os.Error {
var s string
if err := json.Unmarshal(data, &s); err != nil {
return err
}
t, err := time.Parse(Fmt, s)
if err != nil {
// find last "."
i := strings.LastIndex(s, ".")
if i == -1 {
return err
}

// try to parse the slice of the string
t, err = time.Parse(Fmt, s[0:i])
if err != nil {
return err
}
}
*jt = (jTime)(*t)
return nil
}

func (jt jTime) MarshalJSON() ([]byte, os.Error) {
return json.Marshal((*time.Time)(&jt).Format(Fmt))
}

type Foo struct {
T *jTime
I int
}

type Test struct{
Time *jTime
}

func main() {
cjson := []byte("{\"t\":\"2010-12-15T03:56:26.650Z\", \"i\":123}")

var foo Foo;
err := json.Unmarshal(cjson, &foo)

if err != nil {
fmt.Println(err.String())
}

fmt.Println((*time.Time)(foo.T).Format("1 January 2006 @ 3:04
pm") )

}


This leaves me with one issue, having to type the property to call it
properties, like Format() for example

(*time.Time)(foo.T).Format

I could add a method

func(jt *jTime) GetTime() *time.Time{
return (*time.Time)(jt.T);
}

But wondered if there was another way to have a property of type
time.Time that contains the value of the jt.T?

Many thanks for all your help.

peterGo

unread,
Dec 15, 2010, 6:17:52 PM12/15/10
to golang-dev
Russ,

Is there any reason not to make the Go time package ISO 8601
compliant?

"Decimal fractions may also be added to any of the three time
elements. A decimal point, either a comma or a dot
(without any preference as stated most recently in resolution 10 of
the 22nd General Conference CGPM in 2003), is
used as a separator between the time element and its fraction. A
fraction may only be added to the lowest order time
element in the representation."
ISO 8601 Data elements and interchange formats — Information
interchange — Representation of dates and times

Peter

Russ Cox

unread,
Dec 15, 2010, 6:42:31 PM12/15/10
to peterGo, golang-dev
The Go time package does not claim to be ISO 8601 compliant.
http://codereview.appspot.com/3703041 would fix this particular
problem but I don't know that it's really worth it. It still doesn't
comply with ISO 8601 since it does not recognize 57,234 as
a synonym for 57.234.

Russ

Reply all
Reply to author
Forward
0 new messages