How to format a date in Go
Call t.Format and pass a layout — which is an example date, not a set of percent codes: t.Format("2006-01-02 15:04:05"). Writing "%Y-%m-%d" compiles fine and prints %Y-%m-%d, literally.
Most languages spell date formats with escape codes — %Y, yyyy, YYYY. Go spells them by writing out one specific moment: Monday, January 2, 2006 at 3:04:05 PM in zone MST. Read the numbers in order and you get 1 2 3 4 5 6 -7 — month, day, hour, minute, second, year, zone offset. So the layout string 2006-01-02 means “year-month-day” and 02/01/2006 means “day/month/year”. Every example below runs on this page — hit Run, change the layout, and run it again.
1t.Format and the reference layoutRecommended
Format is a method on time.Time: it walks your layout, and wherever it recognises a piece of the reference time it substitutes the corresponding piece of t. Anything it does not recognise — dashes, slashes, the word at, even %Y — is copied through untouched. The snippets here build a fixed instant with time.Date so the output never moves; in your own code that first line is usually t := time.Now().
Output
Prints 2026-08-06, 2026-08-06 15:04:05, 06/08/2026, Thu, 06 Aug 2026 15:04:05 UTC, and then the un-substituted %Y-%m-%d. That last line is the number-one Go date bug: there is no error, no warning, just your format string showing up in production output. Note also that MST in the layout is the zone-name verb — it prints whatever zone t is actually in, here UTC.
2The predefined layouts
The time package exports the common layouts as plain string constants, so you rarely need to spell out the reference time yourself. Go 1.20 added the three people kept hand-writing: time.DateOnly, time.TimeOnly and time.DateTime. For anything a machine will read — JSON, logs, APIs, database columns — reach for time.RFC3339, which is sortable as a string and carries the zone offset with it.
Output
The six lines print 2026-08-06, 15:04:05, 2026-08-06 15:04:05, 2026-08-06T15:04:05Z, Thu, 06 Aug 2026 15:04:05 UTC and 3:04PM; the last line proves the point by printing the constant itself, "2006-01-02 15:04:05". If you are on Go 1.19 or older, just write those strings out — the constants are the only new part.
312-hour clocks, padding and fractions
The reference time was chosen so that the 24-hour hour (15) and the 12-hour hour (3) are different numbers — that is the only way Go can tell them apart. Padding works the same way: 02 is the zero-padded day, 2 is the bare day, and _2 is space-padded to two columns. Fractional seconds are the one place Go uses a real pattern: .000 always prints that many digits, .999 trims trailing zeros.
Output
15:04:05 becomes 3:04:05 PM and 03:04:05 pm; the padding line prints "06" "6" " 6". The line after it is the trap: Jan _2 gives "Aug 6" with the day space-padded, but _1 is not a verb — it prints "_8", a literal underscore followed by the month. Only day-of-month and day-of-year have underscore forms. The last line prints 15:04:05.120 15:04:05.12, showing the difference between .000 and .999.
4Layout cheat sheet
Every layout verb, with what it produced for the instant used above — Thursday 6 August 2026, 15:04:05.12 UTC (day 6, month 8, so the padded and unpadded forms actually differ). All outputs below are from a real run.
Two things are missing on purpose: there is no week-number verb, and there is no locale support — month and weekday names are always English. For localised dates, format the numeric parts with a layout like 2006-01-02 and do the naming yourself, or use the CLDR data in golang.org/x/text.
| Layout piece | Means | Prints | Notes |
|---|---|---|---|
| 2006 | Year, 4 digits | 2026 | The reference year |
| 06 | Year, 2 digits | 26 | Ambiguous — avoid in stored data |
| 01 / 1 | Month number | 08 / 8 | Padded / bare |
| Jan / January | Month name | Aug / August | English only, no locales |
| 02 / 2 / _2 | Day of month | 06 / 6 / " 6" | Padded / bare / space-padded |
| Mon / Monday | Weekday name | Thu / Thursday | Short / long form |
| 15 | Hour, 24-hour | 15 | No bare or 12-hour equivalent |
| 03 / 3 | Hour, 12-hour | 03 / 3 | Pair it with PM |
| 04 / 05 | Minute / second | 04 / 05 | Bare 4 and 5 print 4 and 5 |
| PM / pm | Meridiem | PM / pm | Case of the verb sets the case |
| .000 / .999 | Fractional seconds | .120 / .12 | Fixed width / trailing zeros trimmed |
| -0700 / -07:00 | Numeric zone offset | +0000 / +00:00 | Prints +0000 for UTC |
| Z07:00 | Offset, Z when UTC | Z | What RFC3339 uses |
| MST | Zone abbreviation | UTC | A name, not an offset |
| 002 | Day of year | 218 | Go 1.17+ |
5Parsing a date string back
time.Parse takes the same layout and runs it backwards — one mental model for both directions. It is strict: the input has to match the layout shape exactly, and the resulting date has to be real. That strictness is a feature: Go never guesses whether 06/08/2026 means 6 August or 8 June — you tell it, with the layout.
Output
The first line prints 2026-08-06 00:00:00 +0000 UTC <nil> — that is the default String() rendering of a time.Time, which is why fmt.Println(t) never looks like what you wanted; always Format it. The round-trip prints 2026-08-06T15:04:00Z. The two failures print cannot parse "06/08/2026" as "2006" and day out of range. Fields the layout omits default to zero, so a date-only parse gives you midnight.
6Time zones, UTC and ParseInLocation
A time.Time is an instant plus a location, and Format renders the wall clock in that location. t.In(loc) and t.UTC() change only the rendering, never the instant. The parsing side has a matching pair: time.Parse assumes UTC when the input carries no offset, while time.ParseInLocation reads the same digits as a wall clock in the zone you hand it.
Output
The same instant prints as 2026-08-06T15:04:05Z and 2026-08-06T11:04:05-04:00, and .UTC() brings it straight back — no arithmetic happened. The zone-verb line gives 2026-08-06 11:04:05 -0400 EDT, picking up daylight time from the tz database automatically. At the bottom, Parse yields 2026-08-06T15:04:05Z while ParseInLocation yields 2026-08-06T15:04:05-04:00, i.e. 19:04:05Z — four hours apart from byte-identical input. Store UTC, format on the way out.
Frequently asked questions
Why does Go use 2006-01-02 as a date format?
Because a Go layout is an example of the format, not a set of escape codes. The example is always the same reference instant — Mon Jan 2 15:04:05 MST 2006 — whose components are the digits 1 through 7 in order: month 1, day 2, hour 3, minute 4, second 5, year 6, zone offset -7. So 2006-01-02 reads as year-month-day and 02/01/2006 reads as day/month/year. The upside is that a layout looks exactly like its output; the downside is that a typo like 2021-01-02 silently prints a literal 2021.
Why does %Y-%m-%d print literally in Go?
Because Go has no strftime-style verbs. Format substitutes only the pieces of its reference time and copies everything else through verbatim, so %Y-%m-%d is just five unrecognised characters and prints as %Y-%m-%d. There is no error and no warning — use 2006-01-02 instead.
What is the difference between time.Parse and time.ParseInLocation?
Both take the same layout. When the input string carries no zone offset, time.Parse interprets it as UTC, while time.ParseInLocation interprets it as a wall clock in the *time.Location you pass. Parsing "2026-08-06 15:04:05" with time.Parse gives 2026-08-06T15:04:05Z; parsing it in America/New_York gives 2026-08-06T15:04:05-04:00, which is 19:04:05Z — four hours apart from identical input. Use ParseInLocation whenever the string came from a human in a known zone.
How do I format a date as YYYY-MM-DD in Go?
Use t.Format("2006-01-02"), or the Go 1.20+ constant t.Format(time.DateOnly) which is exactly that string. For a full timestamp use time.DateTime (2006-01-02 15:04:05) or, for anything machine-readable, time.RFC3339 which produces 2026-08-06T15:04:05Z.
Why does fmt.Println(t) print a weird timestamp?
That is time.Time.String(), which uses the fixed layout 2006-01-02 15:04:05.999999999 -0700 MST — so you get something like 2026-08-06 00:00:00 +0000 UTC. It is a debugging representation, not a format you should ship. Call t.Format(...) with the layout you actually want.
How do I get the current date and time in Go?
time.Now() returns the current instant in the local zone; time.Now().UTC() gives it in UTC. Then format it, e.g. time.Now().Format(time.RFC3339). The examples on this page build a fixed instant with time.Date instead, so the output is stable every time you press Run — swap in time.Now() and everything works the same.
Can Go format dates in other languages or locales?
Not in the standard library. Jan, January, Mon and Monday always render English names, and there is no week-number or era verb. For localised output, format the numeric parts with a layout like 2006-01-02 and do the naming yourself, or use the CLDR data in golang.org/x/text.