The thing about dates

Last week I started to challenge myself with #100DaysOfCode. During the week I wrote a Twitch Bot that connects to a list of twitch channels and monitors channel followers and provides some statistics like time watched. Common enough, the follower API provides the time when the user followed the channel and in order to store it to the database with Go, I wanted first to convert it to a time.Time.

So, a bit of background - why I almost never deal with dates? PHP was my bread and butter for the last decade and a half, and this language has a very convenient strtotime function that will convert almost any common format to a unix timestamp. Newer PHP’s have date_create_from_format, or if you really needed to, there was always strptime.

Even for the timestamp provided by Twitch, strtotime does the job:

# php -r 'echo strtotime("2015-02-12T04:15:49Z");'

"Learning Go? Tips & tricks: How to parse and print dates #golang" via @TitPetric

Click to Tweet

Ok, so one would expect to have the same convenience function in Go. In fact, time does provide time.Parse. The description is a bit vague in comparison with what I’m used to, so I go to the sample:

// longForm shows by example how the reference time would be represented in
// the desired layout.
const longForm = "Jan 2, 2006 at 3:04pm (MST)"
t, _ := time.Parse(longForm, "Feb 3, 2013 at 7:54pm (PST)")

What? We’re parsing one date with another date? Back-track to the description of time.Format:

The layout string used by the Parse function and Format method shows by example how the reference time should be represented. We stress that one must show how the reference time is formatted, not a time of the user’s choosing. Thus each layout string is a representation of the time stamp,

Jan 2 15:04:05 2006 MST

An easy way to remember this value is that it holds, when presented in this order, the values (lined up with the elements above):

1 2 3 4 5 6 -7

So, that’s a first. The reference date/time is used for formatting, and in case you didn’t get it after reading it twice, that reference date can be put into a different order which should make it clearer:

Jan(1 month) 2(2 day) 15(3PM hr):04(4 min),(5 sec), 2006(6 years), MST(-7)

The time is 15 to avoid ambiguity between 3am and 3pm. In fact the documentation lists a better example:

01/02 03:04:05PM '06 -0700

Somebody told me this week that Go doesn’t seem to be a good choice for a programming language since you can learn it enough to use it in a few days. I literally wrote two books on the subject of Go, and I can honestly say that I’m still learning. This was a new thing for me.

I can’t say that I like the time.Format as it is, but I’m pretty sure there are packages that implement things like strftime in Go. There’s at least 4 for strftime and one for strptime. In fact, this one guy also implemented a brute-force version of something like strtotime. Some constants are used in this last example, which are good to be aware of:

const (
  ANSIC       = "Mon Jan _2 15:04:05 2006"
  UnixDate    = "Mon Jan _2 15:04:05 MST 2006"
  RubyDate    = "Mon Jan 02 15:04:05 -0700 2006"
  RFC822      = "02 Jan 06 15:04 MST"
  RFC822Z     = "02 Jan 06 15:04 -0700" // RFC822 with numeric zone
  RFC850      = "Monday, 02-Jan-06 15:04:05 MST"
  RFC1123     = "Mon, 02 Jan 2006 15:04:05 MST"
  RFC1123Z    = "Mon, 02 Jan 2006 15:04:05 -0700" // RFC1123 with numeric zone
  RFC3339     = "2006-01-02T15:04:05Z07:00"
  RFC3339Nano = "2006-01-02T15:04:05.999999999Z07:00"
  Kitchen     = "3:04PM"
  Stamp      = "Jan _2 15:04:05"
  StampMilli = "Jan _2 15:04:05.000"
  StampMicro = "Jan _2 15:04:05.000000"
  StampNano  = "Jan _2 15:04:05.000000000"

In fact the date produced by Twitch seems to be… missing. Also the very common MySQL datetime formats are omitted from the handy list of constants. But I guess complaining about that is a slippery slope. As many things, I’m betting non-RFC standard dates would mean advocating some bad practices. The newer PHP DateTime class follows the same conventions.

For what I care about, Twitch sends out something similar to RFC3339, but also provides fractions… sometimes.

layout := "2006-01-02T15:04:05.000000Z"
if !strings.Contains(v.DateCreated, ".") {
  layout = "2006-01-02T15:04:05Z"

Another note: if that first layout with fractional seconds would be written with three zeros, parsing a date string with 6 digits precision will not work. Optionality is not within reach of time.Parse, so you’ll have to have some detection if you want to recover from variable inputs.

While I have you here...

It would be great if you buy one of my books:

I promise you'll learn a lot more if you buy one. Buying a copy supports me writing more about similar topics. Say thank you and buy my books.

Feel free to send me an email if you want to book my time for consultancy/freelance services. I'm great at APIs, Go, Docker, VueJS and scaling services, among many other things.