How to convert a time to UTC before marshaling as JSON in Go?

1k views Asked by At

I'm trying to define a Time struct which implements the Marshaler interface such that, when it is marshaled to JSON, it is represented in the format YYYY-mm-ddTHH:MM:SSZ, that is, the time is converted to UTC and rounded to the nearest second. I've tried the following program:

package main

import (
    "encoding/json"
    "fmt"
    "log"
    "time"
)

type Time struct {
    time.Time
}

func (t *Time) MarshalJSON() ([]byte, error) {
    return []byte(t.Time.UTC().Round(time.Second).Format(time.RFC3339)), nil
}

func main() {
    tm := time.Now()
    // tm := time.Now().UTC().Round(time.Second)

    tmJSON, err := json.Marshal(tm)
    if err != nil {
        log.Fatalf("marshal time: %v", err)
    }

    fmt.Println(string(tmJSON))
}

When I run this, however, it prints

> go run main.go
"2022-12-07T16:32:51.494597-08:00"

If, by contrast, I pass in time.Now().UTC().Round(time.Second) as the input to be marshaled (i.e., use the commented-out line in the snippet above), I get the desired output:

> go run main.go
"2022-12-08T00:41:10Z"

My question is: why can't I perform the conversion to UTC and rounding to the nearest second in the MarshalJSON method itself?

2

There are 2 answers

0
0x4e696b68696c On BEST ANSWER

what are you trying to do?

I tried running your MarshalJSON function and it works as expected

here is what I tried to do:

package main

import (
    "encoding/json"
    "fmt"
    "log"
    "time"
)

type Time struct {
    time.Time
}

func (t *Time) MarshalJSON() ([]byte, error) {
    return []byte(t.Time.UTC().Round(time.Second).Format(time.RFC3339)), nil
}

func main() {
    // tm := time.Now().UTC()
    tm := time.Now().UTC().Round(time.Second)

    tmJSON, err := json.Marshal(tm)
    if err != nil {
        log.Fatalf("marshal time: %v", err)
    }

    fmt.Println(string(tmJSON))

    marshal_time := Time{time.Now().UTC()}
    byt_arr, _ := marshal_time.MarshalJSON()
    fmt.Println(string(byt_arr))
}

and i got the following output:

"2022-12-08T04:41:59Z"
2022-12-08T04:41:59Z

The first line is your previous output and the second output is of your MarshalJSON function.

0
Sabyasachi Patra On

You can use AppendFormat to convert your time string into buffer.

Also in your question you are not initialising your Time struct for Marshalling.

Here is a probable solution

package main

import (
    "encoding/json"
    "errors"
    "fmt"
    "log"
    "time"
)

type Time struct {
    time.Time
}

func (t *Time) MarshalJSON() ([]byte, error) {
    if y := t.Year(); y < 0 || y >= 10000 {
        return nil, errors.New("Time.MarshalJSON: year outside of range [0,9999]")
    }

    b := make([]byte, 0, len(time.RFC3339)+2)
    b = append(b, '"')
    b = t.UTC().Round(time.Second).AppendFormat(b, time.RFC3339)
    b = append(b, '"')
    return b, nil
}

func main() {
    now := time.Now()
    mt := &Time{now}
    bytArr, err := json.Marshal(mt)
    if err != nil {
        log.Fatalf("marshal time: %v", err)
    }

    fmt.Println(string(bytArr))
}