How to parse non standard time format from json

I wrote a package for handling yyyy-MM-dd and yyyy-MM-ddThh:mm:ss dates at https://github.com/a-h/date

It uses the type alias approach in the answer above, then implements the MarshalJSON and UnmarshalJSON functions with a few alterations.

// MarshalJSON outputs JSON.
func (d YYYYMMDD) MarshalJSON() ([]byte, error) {
    return []byte("\"" + time.Time(d).Format(formatStringYYYYMMDD) + "\""), nil
}

// UnmarshalJSON handles incoming JSON.
func (d *YYYYMMDD) UnmarshalJSON(b []byte) (err error) {
    if err = checkJSONYYYYMMDD(string(b)); err != nil {
        return
    }
    t, err := time.ParseInLocation(parseJSONYYYYMMDD, string(b), time.UTC)
    if err != nil {
        return
    }
    *d = YYYYMMDD(t)
    return
}

It's important to parse in the correct timezone. My code assumes UTC, but you may wish to use the computer's timezone for some reason.

I also found that solutions which involved using the time.Parse function leaked Go's internal mechanisms as an error message which clients didn't find helpful, for example: cannot parse "sdfdf-01-01" as "2006". That's only useful if you know that the server is written in Go, and that 2006 is the example date format, so I put in more readable error messages.

I also implemented the Stringer interface so that it gets pretty printed in log or debug messages.


If there are lots of struct and you just implement custom marshal und unmarshal functions, that's a lot of work to do so. You can use another lib instead,such as a json-iterator extension jsontime:

import "github.com/liamylian/jsontime"

var json = jsontime.ConfigWithCustomTimeFormat

type Book struct {
    Id        int           `json:"id"`
    UpdatedAt *time.Time    `json:"updated_at" time_format:"sql_date" time_utc:"true"`
    CreatedAt time.Time     `json:"created_at" time_format:"sql_datetime" time_location:"UTC"`
}

That's a case when you need to implement custom marshal and unmarshal functions.

UnmarshalJSON(b []byte) error { ... }

MarshalJSON() ([]byte, error) { ... }

By following the example in the Golang documentation of json package you get something like:

// First create a type alias
type JsonBirthDate time.Time

// Add that to your struct
type Person struct {
    Name string `json:"name"`
    BirthDate JsonBirthDate `json:"birth_date"`
}

// Implement Marshaler and Unmarshaler interface
func (j *JsonBirthDate) UnmarshalJSON(b []byte) error {
    s := strings.Trim(string(b), "\"")
    t, err := time.Parse("2006-01-02", s)
    if err != nil {
        return err
    }
    *j = JsonBirthDate(t)
    return nil
}
    
func (j JsonBirthDate) MarshalJSON() ([]byte, error) {
    return json.Marshal(time.Time(j))
}

// Maybe a Format function for printing your date
func (j JsonBirthDate) Format(s string) string {
    t := time.Time(j)
    return t.Format(s)
}

Tags:

Time

Go