Parsing RFC-3339 / ISO-8601 date-time string in Go
Asked Answered
S

8

254

I tried parsing the date string "2014-09-12T11:45:26.371Z" in Go. This time format is defined as:

Code

layout := "2014-09-12T11:45:26.371Z"
str := "2014-11-12T11:45:26.371Z"
t, err := time.Parse(layout , str)

I got this error:

parsing time "2014-11-12T11:47:39.489Z": month out of range

How can I parse this date string?

Stefaniastefanie answered 15/9, 2014 at 9:47 Comment(2)
For future readers, I wrote some exercises for practicing date parsing github.com/soniah/date_practiceKook
Your layout should exactly be 2006-01-02T15:04:05.000Z due some crazy go standardMink
V
286

Use the exact layout numbers described here and a nice blogpost here.

so:

layout := "2006-01-02T15:04:05.000Z"
str := "2014-11-12T11:45:26.371Z"
t, err := time.Parse(layout, str)

if err != nil {
    fmt.Println(err)
}
fmt.Println(t)

gives:

>> 2014-11-12 11:45:26.371 +0000 UTC

I know. Mind boggling. Also caught me first time. Go just doesn't use an abstract syntax for datetime components (YYYY-MM-DD), but these exact numbers (I think the time of the first commit of go Nope, according to this. Does anyone know?).

Valverde answered 15/9, 2014 at 9:55 Comment(9)
Layout numbers? What? Why? Argh!Theophilus
What were they thinking ! ? or smoking ?Incapable
I might be a little late with comments here, but don't be scared of layout with numbers, just use constants and your code would be clean :) e.g. time.RFC3339Prehuman
For those that don't get the layout numbers, I admit it's very foreign at first glance, but once you get used to it, I think it makes at least as much sense as typical layout devices ('Do I use "D", "d", "dd", "DD", etc?), and probably more sense. You just have to know about it first.Ahron
It's for mnemonic purpose, that is, you just have to remember 1, 2, 3, 4, 5, 6, 7 these letters. There's a great article discussing this: medium.com/@simplyianm/…Expedite
@threeve Typical layout devices: "Do I use 'D', 'd', 'dd' or 'DD'?" "What does this code do? There's a DD, so that's D for day". Layout numbers: "Do I use 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 00, 01, 02, 03, 04, 05, 06, 07, 08 or 09? Hold on, some of them were greater than 10?" "What does this code do? There's a 06 so that's ... umm... guess I need to look that up. Hold on, now I forgot what the first number meant again".Barnett
@JishnuPrathap I want to know what they took, how much and who their supplier is.Flagstone
@Expedite why not 2021-12-31 for the date? that is much more readable and understandable :(Rogers
@JackySupit they needed a digit to represent each field, so they went with 1, 2, 3, 4, 5, 6, 7. The 'only' problem is 6 is out of place for the year, and as Bernhard Barker points out once more complex requirements appear we're no longer better than with letters.Roath
R
110

As answered but to save typing out "2006-01-02T15:04:05.000Z" for the layout, you could use the package's constant RFC3339.

str := "2014-11-12T11:45:26.371Z"
t, err := time.Parse(time.RFC3339, str)

if err != nil {
    fmt.Println(err)
}
fmt.Println(t)

https://play.golang.org/p/Dgu2ZvHwTh

Rocky answered 28/5, 2017 at 23:40 Comment(3)
Additionally, if you look at the package constants (linked in the answer above) there are a bunch of other common formats provided that can be used. If you need something slightly different, use them as a starting point.Pola
This and several answers recommend 2006-01-02T15:04:05.000Z and mention that Go's time.RFC3339 would also work. But time.RFC3339 = "2006-01-02T15:04:05Z07:00". Are these two formats exactly equivalent insofar as what time.Parse and time.ParseInLocation will do?Combustible
That's right @Miles, this test confirms it play.golang.org/p/T3dW1kTeAHlRocky
O
105

The layout to use is indeed "2006-01-02T15:04:05.000Z" described in RickyA's answer.
It isn't "the time of the first commit of go", but rather a mnemonic way to remember said layout.
See pkg/time:

The reference time used in the layouts is:

Mon Jan 2 15:04:05 MST 2006

which is Unix time 1136239445.
Since MST is GMT-0700, the reference time can be thought of as

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

(1,2,3,4,5,6,7, provided you remember that 1 is for the month, and 2 for the day, which is not easy for an European like myself, used to the day-month date format)

As illustrated in "time.parse : why does golang parses the time incorrectly?", that layout (using 1,2,3,4,5,6,7) must be respected exactly.

Orthopterous answered 15/9, 2014 at 10:22 Comment(4)
Yeah that catches Australians out too! MM/DD just does not compute for me and I have to keep looking at it.Waylonwayman
@SimonWhitehead I agree. At least, once I look it up, I know what YY, MM, DD, hh, mm, ss stand for and I can re-order them easily. With Go, even after looking it up, I need to remember what 1, 2, 3, 4... stand for.Orthopterous
The way I remember it is: Step 1) The "proper" date ordering is year > month > day > hour > minute > second > etc. (Because mixed-endian would just be non-sensically arbitrary and inconsistent and for dates big-endian is preferable over little-endian because it's sort-friendly.) This would get us 1 (year), 2 (month), 3 (day), [...] Step 2) Go/Google are American and Americans put their year at the end of their dates, so instead it's 1 (month), 2 (day), [...], n (year) Step 3) The timezone goes after everything else because timezones are an additional abstraction layer.Leatherneck
@Leatherneck Yes... I too miss web.archive.org/web/20180501100155/http://… (from github.com/bdotdub/fuckinggodateformat). I mean, strftime FTW.Orthopterous
S
46

This is rather late to the party, and not really saying anything that hasn't been already said in one form or another, mostly through links above, but I wanted to give a TL;DR recap to those with less attention span:

The date and time of the go format string is very important. It's how Go knows which field is which. They are generally 1-9 left to right as follows:

  • January / Jan / january / jan / 01 / _1 (etc) are for month
  • 02 / _2 are for day of month
  • 15 / 03 / _3 / PM / P / pm /p are for hour & meridian (3pm)
  • 04 / _4 are for minutes
  • 05 / _5 are for seconds
  • 2006 / 06 are for year
  • -0700 / 07:00 / MST are for timezone
  • .999999999 / .000000000 etc are for partial seconds (I think the distinction is if trailing zeros are removed)
  • Mon / Monday are day of the week (which 01-02-2006 actually was),

So, Don't write "01-05-15" as your date format, unless you want "Month-Second-Hour"

(... again, this was basically a summary of above.)

Spinet answered 19/8, 2018 at 8:19 Comment(2)
So how does it know 02/01/2006 is Feb 1st 2006 or Jan 2nd 2006?Wethington
@Wethington in the format string 01 will be the month and 02 will be the day, regardless of where they appear. I have no idea why Go decided to go with this formatting spec, but that's how it works.Roath
C
34

I will suggest using time.RFC3339 constant from time package. You can check other constants from time package. https://golang.org/pkg/time/#pkg-constants

package main

import (
    "fmt"
    "time"
)

func main() {
    fmt.Println("Time parsing");
    dateString := "2014-11-12T11:45:26.371Z"
    time1, err := time.Parse(time.RFC3339,dateString);
    if err!=nil {
    fmt.Println("Error while parsing date :", err);
    }
    fmt.Println(time1); 
}
Cardio answered 18/2, 2018 at 15:52 Comment(1)
Did you mean to use semicolons?Gallstone
R
12

This might be super late, but this is for people that might stumble on this problem and might want to use external package for parsing date string.

I've tried looking for a libraries and I found this one:

https://github.com/araddon/dateparse

Example from the README:

package main

import (
    "flag"
    "fmt"
    "time"

    "github.com/apcera/termtables"
    "github.com/araddon/dateparse"
)

var examples = []string{
    "May 8, 2009 5:57:51 PM",
    "Mon Jan  2 15:04:05 2006",
    "Mon Jan  2 15:04:05 MST 2006",
    "Mon Jan 02 15:04:05 -0700 2006",
    "Monday, 02-Jan-06 15:04:05 MST",
    "Mon, 02 Jan 2006 15:04:05 MST",
    "Tue, 11 Jul 2017 16:28:13 +0200 (CEST)",
    "Mon, 02 Jan 2006 15:04:05 -0700",
    "Thu, 4 Jan 2018 17:53:36 +0000",
    "Mon Aug 10 15:44:11 UTC+0100 2015",
    "Fri Jul 03 2015 18:04:07 GMT+0100 (GMT Daylight Time)",
    "12 Feb 2006, 19:17",
    "12 Feb 2006 19:17",
    "03 February 2013",
    "2013-Feb-03",
    //   mm/dd/yy
    "3/31/2014",
    "03/31/2014",
    "08/21/71",
    "8/1/71",
    "4/8/2014 22:05",
    "04/08/2014 22:05",
    "4/8/14 22:05",
    "04/2/2014 03:00:51",
    "8/8/1965 12:00:00 AM",
    "8/8/1965 01:00:01 PM",
    "8/8/1965 01:00 PM",
    "8/8/1965 1:00 PM",
    "8/8/1965 12:00 AM",
    "4/02/2014 03:00:51",
    "03/19/2012 10:11:59",
    "03/19/2012 10:11:59.3186369",
    // yyyy/mm/dd
    "2014/3/31",
    "2014/03/31",
    "2014/4/8 22:05",
    "2014/04/08 22:05",
    "2014/04/2 03:00:51",
    "2014/4/02 03:00:51",
    "2012/03/19 10:11:59",
    "2012/03/19 10:11:59.3186369",
    // Chinese
    "2014年04月08日",
    //   yyyy-mm-ddThh
    "2006-01-02T15:04:05+0000",
    "2009-08-12T22:15:09-07:00",
    "2009-08-12T22:15:09",
    "2009-08-12T22:15:09Z",
    //   yyyy-mm-dd hh:mm:ss
    "2014-04-26 17:24:37.3186369",
    "2012-08-03 18:31:59.257000000",
    "2014-04-26 17:24:37.123",
    "2013-04-01 22:43",
    "2013-04-01 22:43:22",
    "2014-12-16 06:20:00 UTC",
    "2014-12-16 06:20:00 GMT",
    "2014-04-26 05:24:37 PM",
    "2014-04-26 13:13:43 +0800",
    "2014-04-26 13:13:44 +09:00",
    "2012-08-03 18:31:59.257000000 +0000 UTC",
    "2015-09-30 18:48:56.35272715 +0000 UTC",
    "2015-02-18 00:12:00 +0000 GMT",
    "2015-02-18 00:12:00 +0000 UTC",
    "2017-07-19 03:21:51+00:00",
    "2014-04-26",
    "2014-04",
    "2014",
    "2014-05-11 08:20:13,787",
    // mm.dd.yy
    "3.31.2014",
    "03.31.2014",
    "08.21.71",
    //  yyyymmdd and similar
    "20140601",
    // unix seconds, ms
    "1332151919",
    "1384216367189",
}

var (
    timezone = ""
)

func main() {
    flag.StringVar(&timezone, "timezone", "UTC", "Timezone aka `America/Los_Angeles` formatted time-zone")
    flag.Parse()

    if timezone != "" {
        // NOTE:  This is very, very important to understand
        // time-parsing in go
        loc, err := time.LoadLocation(timezone)
        if err != nil {
            panic(err.Error())
        }
        time.Local = loc
    }

    table := termtables.CreateTable()

    table.AddHeaders("Input", "Parsed, and Output as %v")
    for _, dateExample := range examples {
        t, err := dateparse.ParseLocal(dateExample)
        if err != nil {
            panic(err.Error())
        }
        table.AddRow(dateExample, fmt.Sprintf("%v", t))
    }
    fmt.Println(table.Render())
}
Roquefort answered 11/8, 2017 at 3:40 Comment(1)
Unfortunately, there is an ambiguity in day month order. European date format gives day first and month second, and US time format use the reverse. A date like 3/5/2004 is ambiguous. The date is valid in US and European format, but 3 and 5 may correspond to day and month or the reverse. dateParse assume US format.Electrotonus
E
2

For those of you out there that are encountering this, use the time.RFC3339 versus the string constant of "2006-01-02T15:04:05.000Z". And here is the reason why:

regDate := "2007-10-09T22:50:01.23Z"

layout1 := "2006-01-02T15:04:05.000Z"
t1, err := time.Parse(layout1, regDate)

if err != nil {
    fmt.Println("Static format doesn't work")
} else {
    fmt.Println(t1)
}

layout2 := time.RFC3339
t2, err := time.Parse(layout2, regDate)

if err != nil {
    fmt.Println("RFC format doesn't work") // You shouldn't see this at all
} else {
    fmt.Println(t2)
}

This will produce the following result:

Static format doesn't work
2007-10-09 22:50:01.23 +0000 UTC

Here is the Playground Link

Effervescent answered 6/1, 2021 at 23:56 Comment(0)
M
-2

If you have worked with time/date formatting/parsing in other languages you might have noticed that the other languages use special placeholders for time/date formatting. For eg ruby language uses

%d for day
%Y for year

etc. Golang, instead of using codes such as above, uses date and time format placeholders that look like date and time only. Go uses standard time, which is:

Mon Jan 2 15:04:05 MST 2006  (MST is GMT-0700)
or 
01/02 03:04:05PM '06 -0700

So if you notice Go uses

01 for the day of the month,
02 for the month
03 for hours,
04 for minutes
05 for second
and so on

Therefore for example for parsing 2020-01-29, layout string should be 06-01-02 or 2006-01-02.

You can refer to the full placeholder layout table at this link - https://golangbyexample.com/parse-time-in-golang/

Markmarkdown answered 5/2, 2020 at 13:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.