Today I Learned: Dealing with JSON DateTime when Unmarshal in Golang.

Written by imantumorang | Published 2018/09/20
Tech Story Tags: golang | today-i-learned | json | rfc3339 | programming | hackernoon-es

TLDR Today I Learned: Dealing with JSON DateTime when Unmarshal in Golang. Iman Tumorang Software Engineer: "Beware the date-time standard!!" I’m not saying this is a bug, but this is only my story because of my lack of experience when dealing with DateTime and timezone in. Golang.com/labstack/echo as my routing library to ease me to handle my request. With echo, to receive the response body and unmarshal it to my struct, I can do it with a simple code like below.via the TL;DR App

Beware the date-time standard!!

Golang taken from google image search
So today’s story is about the weird behavior when unmarshalling DateTime from JSON in Golang. I’m not saying this is a bug, but this is only my story because of my lack experience when dealing with DateTime and timezone in Golang
It happens when I want to make a CRUD API system in Golang. So, I have an endpoint let’s say:
/event
that will receive an event object from JSON.
{
  "title": "title here in string",
  "place": "place name here in string",
  "start_time": "date time here in string"
}
I’m using labstack/echo as my routing library to ease me to handle my request. With echo, to receive the response body and unmarshal it to my struct, I can do it with a simple code like below:
package main

import (
	"log"
	"net/http"
	"time"

	"github.com/labstack/echo"
)

type Event struct {
	Title     string    `json:"title"`
	Place     string    `json:"place"`
	StartTime time.Time `json:"start_time"`
	CreatedAt time.Time `json:"created_at"`
	UpdatedAt time.Time `json:"updated_at"`
}

func CreateEvent(c echo.Context) error {
	e := new(Event)
	err := c.Bind(&e)
	if err != nil {
		return err
	}
	now := time.Now()
	e.CreatedAt = now
	e.UpdatedAt = now

	// Other code here
	//  ....
	//  Store to DB
	//  ...
	return c.JSON(http.StatusCreated, e)
}

func main() {

	e := echo.New()

	e.POST("/event", CreateEvent)

	if err := e.Start(":9090"); err != nil {
		log.Fatal(err)
	}
}
So, when I want to test it, I made the request body like below, I just copy-paste it from our API documentation example. Because at first, I don’t even think it matter.
{
  "title": "Core i13 Anniversarry",
  "place": "Ancol Beach",
  "start_time": "2018-09-22T12:42:31Z"
}
The API already deployed to the staging server, so I test it directly from Postman to the staging server. But after fetching all the stored event-item, the start_time is different to what I posted in the request body. It becomes like this below.
{
  "id": "1", // auto increment from database
  "title": "Core i13 Anniversarry",
  "place": "Ancol Beach",
  "start_time": "2018-09-22T19:42:31+07:00",
  "created_at": "2018-09-22T16:35:08+07:00",
  "updated_at": "2018-09-22T16:35:08+07:00"
}
And in the database it stored like this:
Look the
start_time
field. It changed from what I posted. What I want is, the stored data is same as what I posted from Postman.
I found that the issue happens because of the timezone obviously. So I just need to take care of the timezone. That was my first thought. I think my systems have a bug about this.
Resolving the Issue
To fix this issue, I’m doing all this action below.
Looking for the connection string in the connection driver
Because I’m using the MySQL as my DB storage, so I need a driver to connect my application with the MySQL. I’m using github.com/go-sql-driver/mysql as my connection driver. So after looking for the documentation and checking my connection string, nothing wrong happen. I made it correctly.
dsn := root:root@tcp(127.0.0.1:3306)/event?parseTime=true&loc=Asia%2FJakarta&charset=utf8mb4&collation=utf8mb4_unicode_ci

Rebuild the Datetime manually

Still curious, why this is happening. Then I’m trying to find any Q/A or Issue about this in StackOverflow or even in GitHub.
To be honest, it takes up to 2 hours for me when trying to fix this issue 🤦‍
Until then, I got frustrated. So I have 2 option how to solve this.
I will rebuild the start_time before stored to Database.Asking my team-mates (* even it feel so silly when asking this anyway🤦‍)
But, just want to give more effort 😶, I made the fix with rebuilding the DateTime.
startTime := e.StartTime
loc, _ := time.LoadLocation("Asia/Jakarta")
localStartTime := time.Date(startTime.Year(), startTime.Month(), startTime.Day(), startTime.Hour(), startTime.Minute(), startTime.Second(), startTime.Nanosecond(), loc)
Then, just out of curiosity, I ask my team about the issue. Then they look how I reproduce it, same as me, they also curious why this is happening. Until just a few minutes, one of my team found the reasons why this is happening.
This is happening because I made a wrong timezone offset in my request body.
{
  "title": "Core i13 Anniversarry",
  "place": "Ancol Beach",
  "start_time": "2018-09-22T12:42:31Z" // look for the `Z` indicator
}
Because I lived in +7.00 timezone (Asia/Jakarta), it should be included in my JSON.
So, when I change the
start_time
from:
2018-09-22T12:42:31<strong>Z</strong>
into
2018-09-22T12:42:31<strong>+07:00</strong>
it solves my problem. Nothing wrong happened to my system. All is work perfectly.
When I realized this, I feel so dumb 🤦‍. I’ve spent my two hours of my life for nothing 🤦‍.
Lesson To be Learned
  • Always ask your team-mates first (if they were available anyway)
  • 2018–09–22T12:42:31+07:00
    is the RFC 3339 format, every single character is matter.
If you think this article is worth to read, give a clap or share to your network circle, so everyone can also read this. Follow for more stories like this!

Written by imantumorang | Software Engineer
Published by HackerNoon on 2018/09/20