Go datetime parse
last modified October 30, 2026
In this article we show how to parse date and time strings in Go. Parsing is
the reverse of formatting: a layout describes the expected text, and the
time package turns the text into a time.Time value.
The two functions used for parsing are:
func Parse(layout, value string) (Time, error) func ParseInLocation(layout, value string, loc *Location) (Time, error)
Parse interprets the text in UTC unless the text itself contains a
time zone. ParseInLocation uses the given location for values
without a zone.
The layout uses the reference instant
Mon Jan 2 15:04:05 MST 2006. Go does not use format specifiers
such as yyyy-mm-dd; the components of the reference value decide
which parts of the input are read. A detailed description of the components is
in the Formatting Date and Time in Go
article.
All examples in this article were compiled and run with Go 1.27.0.
Parsing a date
A date is parsed with a layout that contains the year, the month and the day in the same order as the input. The result has the time set to midnight.
package main
import (
"fmt"
"time"
)
func main() {
value := "2022/05/12"
t, err := time.Parse("2006/01/02", value)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(t)
fmt.Printf("%v %v %v\n", t.Year(), t.Month(), t.Day())
}
The layout 2006/01/02 matches the slashes of the input. The
parsed value is in UTC because the text has no zone, and the missing time
components default to zero.
$ go run main.go 2022-05-12 00:00:00 +0000 UTC 2022 May 12
Parsing a time of day
A layout may contain only the time. The missing date is then filled with the zero date, January 1 of year 0.
package main
import (
"fmt"
"time"
)
func main() {
value := "14:55:23"
t, err := time.Parse("15:04:05", value)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(t)
fmt.Println(t.Format(time.Kitchen))
}
The zero date is useful when only the clock time matters, but it is not a valid calendar date. Use the fields of the parsed value rather than the whole value in that case.
$ go run main.go 0000-01-01 14:55:23 +0000 UTC 2:55PM
Parse errors
When the text does not match the layout, Parse returns the zero
time and an error. The error names the offending part of the input.
package main
import (
"fmt"
"time"
)
func main() {
cases := [][2]string{
{"2022-13-12", "2006-01-02"},
{"2022-02-30", "2006-01-02"},
{"2022-05-12 10:00", "2006-01-02"},
{"12/05/2022", "2006-01-02"},
{"2022-5-2", "2006-01-02"},
}
for _, c := range cases {
t, err := time.Parse(c[1], c[0])
fmt.Printf("%q -> %v | %v\n", c[0], t, err)
}
}
A month outside 1 to 12 and a day that does not exist in the given month are
range errors. Extra text after a complete match is reported separately. A
different order of the fields, or a single-digit day where the layout requires
02, fails with cannot parse.
The zero value printed for a failed parse is January 1 of year 1, not the zero date used by a successful parse of a time-only value.
$ go run main.go "2022-13-12" -> 0001-01-01 00:00:00 +0000 UTC | parsing time "2022-13-12": month out of range "2022-02-30" -> 0001-01-01 00:00:00 +0000 UTC | parsing time "2022-02-30": day out of range "2022-05-12 10:00" -> 0001-01-01 00:00:00 +0000 UTC | parsing time "2022-05-12 10:00": extra text: " 10:00" "12/05/2022" -> 0001-01-01 00:00:00 +0000 UTC | parsing time "12/05/2022" as "2006-01-02": cannot parse "12/05/2022" as "2006" "2022-5-2" -> 0001-01-01 00:00:00 +0000 UTC | parsing time "2022-5-2" as "2006-01-02": cannot parse "5-2" as "01"
Predefined layouts
The constants from the time package can be used as layouts for
parsing as well. The next example reads one value per constant.
package main
import (
"fmt"
"time"
)
func main() {
dates := []string{
"Sat May 28 11:54:40 CEST 2022",
"Sat May 28 11:54:40 2022",
"Sat, 28 May 2022 11:54:40 CEST",
"Sat, 28 May 2022 11:54:40 +0200",
"28 May 22 11:54 CEST",
"28 May 22 11:54 +0200",
"Saturday, 28-May-22 11:54:40 CEST",
"2022-05-28T11:54:40+02:00",
"2022-05-28T11:54:40.809289619+02:00",
"Sat May 28 11:54:40 +0200 2022",
"05/28 11:04:40AM '22 +0200",
"May 28 11:54:40",
}
layouts := []string{
time.UnixDate,
time.ANSIC,
time.RFC1123,
time.RFC1123Z,
time.RFC822,
time.RFC822Z,
time.RFC850,
time.RFC3339,
time.RFC3339Nano,
time.RubyDate,
time.Layout,
time.Stamp,
}
for i := range dates {
parsed, err := time.Parse(layouts[i], dates[i])
if err != nil {
fmt.Println(err)
continue
}
fmt.Println(parsed)
}
}
The zone abbreviation in the text is preserved. An unknown abbreviation is
recorded with an offset of zero rather than rejected. A value without any zone,
such as the ANSIC and Stamp inputs, is interpreted as
UTC.
$ go run main.go 2022-05-28 11:54:40 +0200 CEST 2022-05-28 11:54:40 +0000 UTC 2022-05-28 11:54:40 +0200 CEST 2022-05-28 11:54:40 +0200 CEST 2022-05-28 11:54:00 +0200 CEST 2022-05-28 11:54:00 +0200 CEST 2022-05-28 11:54:40 +0200 CEST 2022-05-28 11:54:40 +0200 CEST 2022-05-28 11:54:40.809289619 +0200 CEST 2022-05-28 11:54:40 +0200 CEST 2022-05-28 11:04:40 +0200 CEST 0000-05-28 11:54:40 +0000 UTC
Fractional seconds
A fractional second in the input is accepted even when the layout does not
mention it. The opposite is not true: a layout with .000 requires
the fraction to be present.
package main
import (
"fmt"
"time"
)
func main() {
t, err := time.Parse("2006-01-02T15:04:05", "2026-10-30T09:05:07.25")
fmt.Println(t, err)
t, err = time.Parse("2006-01-02T15:04:05.000", "2026-10-30T09:05:07")
fmt.Println(t, err)
}
The first call keeps the 250 milliseconds. The second fails, because the text ends where the layout expects a decimal point and three digits.
$ go run main.go 2026-10-30 09:05:07.25 +0000 UTC <nil> 0001-01-01 00:00:00 +0000 UTC parsing time "2026-10-30T09:05:07" as "2006-01-02T15:04:05.000": cannot parse "" as ".000"
Zone names and offsets
A zone can be written as an abbreviation, as a numeric offset, or as
Z. An abbreviation that is not in the local zone database becomes
a fabricated zone with a zero offset, which silently changes the instant.
package main
import (
"fmt"
"time"
)
func main() {
t, err := time.Parse(time.RFC1123, "Fri, 30 Oct 2026 09:05:07 XYZ")
if err != nil {
fmt.Println(err)
return
}
name, offset := t.Zone()
fmt.Println(t)
fmt.Println(name, offset)
fmt.Println(t.UTC())
for _, value := range []string{
"2026-10-30T09:05:07Z",
"2026-10-30T09:05:07",
"2026-10-30T09:05:07+05:30",
} {
t, err := time.Parse(time.RFC3339, value)
fmt.Printf("%-26s %v %v\n", value, t, err)
}
}
XYZ is unknown, so it is kept as a name with offset 0 and the
instant equals UTC. RFC 3339 requires the zone: the second value has no zone
and is rejected, while the last one is converted from the +05:30
offset.
$ go run main.go 2026-10-30 09:05:07 +0000 XYZ XYZ 0 2026-10-30 09:05:07 +0000 UTC 2026-10-30T09:05:07Z 2026-10-30 09:05:07 +0000 UTC <nil> 2026-10-30T09:05:07 0001-01-01 00:00:00 +0000 UTC parsing time "2026-10-30T09:05:07" as "2006-01-02T15:04:05Z07:00": cannot parse "" as "Z07:00" 2026-10-30T09:05:07+05:30 2026-10-30 09:05:07 +0530 +0530 <nil>
ParseInLocation
ParseInLocation assigns the given location to a value that has no
zone. The wall clock stays the same, so two locations produce two different
instants.
package main
import (
"fmt"
"log"
"time"
)
func main() {
date := "Sat May 28 11:54:40 2022"
utc, err := time.Parse(time.ANSIC, date)
if err != nil {
log.Fatal(err)
}
fmt.Println("Parse: ", utc)
bratislava, err := time.LoadLocation("Europe/Bratislava")
if err != nil {
log.Fatal(err)
}
local, err := time.ParseInLocation(time.ANSIC, date, bratislava)
if err != nil {
log.Fatal(err)
}
fmt.Println("Bratislava: ", local)
tokyo, err := time.LoadLocation("Asia/Tokyo")
if err != nil {
log.Fatal(err)
}
japan, err := time.ParseInLocation(time.ANSIC, date, tokyo)
if err != nil {
log.Fatal(err)
}
fmt.Println("Tokyo: ", japan)
fmt.Println("same instant: ", local.Equal(japan))
}
All three values show 11:54:40, but they are three different moments. The locations observe summer time on May 28, so Bratislava is two hours ahead of UTC and Tokyo nine hours ahead.
$ go run main.go Parse: 2022-05-28 11:54:40 +0000 UTC Bratislava: 2022-05-28 11:54:40 +0200 CEST Tokyo: 2022-05-28 11:54:40 +0900 JST same instant: false
Month and weekday names
Month and weekday names are matched without regard to case. The form of the
name still has to match the layout: Jan reads an abbreviation and
January a full name.
package main
import (
"fmt"
"time"
)
func main() {
t, err := time.Parse("Jan 2 2006", "OCT 30 2026")
fmt.Println(t, err)
t, err = time.Parse("Monday, 2 January 2006", "friday, 30 october 2026")
fmt.Println(t, err)
}
Both values are accepted and normalized. The weekday in the input is not used to compute the date, so a wrong weekday is not detected.
$ go run main.go 2026-10-30 00:00:00 +0000 UTC <nil> 2026-10-30 00:00:00 +0000 UTC <nil>
Trying several layouts
Real input often comes in more than one format. A common approach is to try a list of layouts and stop at the first one that parses.
package main
import (
"fmt"
"time"
)
func main() {
input := "31/12/2026"
layouts := []struct {
name string
layout string
}{
{"ISO", "2006-01-02"},
{"European", "02.01.2006"},
{"long", "January 2, 2006"},
{"short", "02/01/2006"},
}
for _, l := range layouts {
t, err := time.Parse(l.layout, input)
if err != nil {
continue
}
fmt.Printf("matched %s: %s\n", l.name, t.Format(time.DateOnly))
return
}
fmt.Println("no layout matched")
}
Only the last layout matches. The order of the list matters when two layouts can read the same text, because the first success wins.
$ go run main.go matched short: 2026-12-31
Unix timestamps
A Unix timestamp is not parsed with a layout. The number is converted with
strconv.ParseInt and then passed to time.Unix, which
returns a local time; UTC makes the result independent of the
machine's zone.
package main
import (
"fmt"
"strconv"
"time"
)
func main() {
sec, err := strconv.ParseInt("1793386446", 10, 64)
if err != nil {
fmt.Println(err)
return
}
t := time.Unix(sec, 0).UTC()
fmt.Println(t.Format(time.RFC3339))
milli := time.UnixMilli(1793386446954).UTC()
fmt.Println(milli.Format("2006-01-02 15:04:05.000"))
}
time.Unix takes seconds and nanoseconds, while
time.UnixMilli and time.UnixMicro take the smaller
units directly.
$ go run main.go 2026-10-30T18:54:06Z 2026-10-30 18:54:06.954
Parsing durations
A duration is a different type from a time. time.ParseDuration
accepts a signed sequence of decimal numbers with units such as
h, m, s, ms and
us.
package main
import (
"fmt"
"time"
)
func main() {
d, err := time.ParseDuration("2h45m30s")
if err != nil {
fmt.Println(err)
return
}
fmt.Println(d)
fmt.Println(d.Minutes())
_, err = time.ParseDuration("5 hours")
fmt.Println(err)
}
The units must be written directly after the number and without spaces. The error for the second call names the unknown unit.
$ go run main.go 2h45m30s 165.5 time: unknown unit " hours" in duration "5 hours"
Parsing and formatting
The usual workflow reads text with a layout and then writes it with a different layout or in a different location.
package main
import (
"fmt"
"log"
"time"
)
func main() {
t, err := time.Parse("02 Jan 2006 15:04 MST", "30 Oct 2026 09:05 UTC")
if err != nil {
log.Fatal(err)
}
fmt.Println(t.Format(time.RFC3339))
tokyo, err := time.LoadLocation("Asia/Tokyo")
if err != nil {
log.Fatal(err)
}
fmt.Println(t.In(tokyo).Format("Mon 2 Jan 2006, 15:04 MST"))
}
The same instant is rendered in UTC and in Tokyo. Only the presentation changes; the value itself is not modified.
$ go run main.go 2026-10-30T09:05:00Z Fri 30 Oct 2026, 18:05 JST
Source
In this article we have parsed date and time values in Go. We covered
time.Parse and time.ParseInLocation, the error
messages for invalid input, predefined layouts, fractional seconds, zone
abbreviations and offsets, case-insensitive names, a multi-layout fallback, Unix
timestamps, durations and the round trip from text to a formatted value.
Author
List all Go tutorials.