Learn coding concepts by building real projects with modern technologies

I Wrote Go-TinyDate, The Missing Golang Date Package

By Lane Wagner on Mar 23, 2020

time.Time makes dealing with dates and times in Go a breeze, and it even comes bundled in the standard library! However, a time.Time{} struct uses more than 24 bytes of memory under most conditions, and I’ve run into situations where I need to store millions of them in memory, but all I really needed was a UTC date! Go-TinyDate solves this with just 4 bytes of memory.

Star the Github! https://github.com/wagslane/go-tinydate

How?

Let’s look at the time.Time struct:

type Time struct {
	wall uint64 // 8 bytes
	ext  int64 // b bytes
	loc *Location // 8 bytes if not nil, plus location memory
}

type Location struct {
	name string // unlimited
	zone []zone // unlimited
	tx   []zoneTrans // unlimited
	cacheStart int64 // 8 bytes
	cacheEnd   int64 // 8 bytes
	cacheZone  *zone // 8 bytes if not nil, plus zone
}

type zone struct {
	name   string // unlimited
	offset int    // 4-8 bytes depending on OS
	isDST  bool   // 1 bit
}

type zoneTrans struct {
	when         int64 // 8 bytes
	index        uint8 // 1 byte
	isstd, isutc bool  // 1 bit
}

https://golang.org/src/time/time.go?s=6278:7279#L117

As you can see, depending on how the TimeZone is set, there can be quite a bit of memory allocated just to store a time.Time. Even if there is no location set, the lower-bound is still 16 bytes.

Contrast with a tinydate.TinyDate{}:

type TinyDate struct {
	year uint16 // 2 byte
	month uint8 // 1 byte
	day uint8 // 1 byte
}

Only 4 bytes! We give up the ability to track anything more specific than the date, but often that is all we need.

Learn Go by writing Go code

I'm a senior engineer learning Go, and the pace of Boot.dev's Go Mastery courses has been perfect for me. The diverse community in Discord makes the weekly workshops a blast, and other members are quick to help out with detailed answers and explanations.

- Daniel Gerep from Cassia, Brasil

Quick Start

Create a date and add to it:

package main

import (
    tinydate "github.com/wagslane/go-tinydate"
)

func main(){
    td, err := tinydate.New(2020, 04, 3)
	if err != nil {
		fmt.Println(err.Error())
    }
    
    td = td.Add(time.Hour * 48)
    fmt.Println(td)
    // prints 2020-04-05
}

Or Cast a time to a tinydate and back:

newTinydate, err := FromTime(time.Now())
if err != nil{
    fmt.Println(err.Error())
}
convertedTime := newTinydate.ToTime()

When Should I Use It?

As the TinyDate Readme states, if you aren’t constrained for resources, better to stick with the standard time.Time. But the following situations can be good reasons to switch to TinyDate:

Why No Timezones?

The main reason? Timezones are the most memory heavy part of a time.Time struct, yet the best practice is usually to store dates and times only in UTC. TinyDate stays tiny by always storing dates in UTC, but still gives the ability to calculate dates in other timezones via methods like ParseInLocation FromTime and ToTime.

Learn JavaScript by building real projects

I was a field service engineer and I wanted to learn to code, but work and family limited my options. When I found Boot.dev, the "Intro to Coding in JavaScript" course got me up and running immediately, and I knew I made the right decision as soon as I joined the Discord community.

- Özgür Yildirim from Germany

API

The tinydate.Tinydate API largely mirrors that of time.Time. The only methods missing are the ones that make no sense without timezone or intra-day support. Check out the godoc for reference: https://godoc.org/github.com/wagslane/go-tinydate

If you like the package, give it a Star on Github

Learn to code by building real projects

Related Reading