Dearest learner
I hope this post finds you well.
If you're here because time.Now().Format("YYYY-MM-DD") isn't doing what you expected, pull up a chair. I spent an embarrassing amount of time staring at that exact problem, and today I'd like to save you the trouble.
First, Some Embarrassing Context
If you read my last post, you already know I was building a Net-Cat project in Go and got burned by log.Println() silently adding its own timestamp to my output. Fun times.
Well — I hadn't even gotten to that bug yet. Before I ran into the log problem, I was still fighting a more basic battle: just getting a timestamp to print correctly in the first place.
This is that story.
What I Expected
I come from a JavaScript background. In JS, formatting a date looks something like this:
// JavaScript
const now = new Date();
console.log(now.toISOString()); // 2025-01-15T14:32:05.000Z
And in Python, it's something like:
# Python
from datetime import datetime
print(datetime.now().strftime("%Y-%m-%d")) # 2025-01-15
Notice the pattern? Both use placeholder tokens. YYYY means "put the 4-digit year here." MM means "put the month here." %d means "put the day here." They're basically fill-in-the-blank templates where each token represents a concept.
So when I opened a Go file and typed this, it felt completely natural:
// What I tried first (very wrong)
fmt.Println(time.Now().Format("YYYY-MM-DD"))
I hit run.
The output was: YYYY-MM-DD
Just... the string. Printed back at me, unchanged. No date. No numbers. Nothing.
I sat there for a second. Then I tried "yyyy-mm-dd". Same thing. Then "%Y-%m-%d" like Python. Same thing. Go was just printing whatever I typed, completely unbothered.
The Thing Nobody Warned Me About
Here's what Go actually does, and I wish someone had just said this to me directly on day one:
Go doesn't use placeholder tokens. It uses a real, specific reference date.
That reference date is:
Mon Jan 2 15:04:05 MST 2006
Or written out in numbers:
01/02 03:04:05PM '06 -0700
You use that exact date — in whatever arrangement you want — as your format string. Go looks at your string, recognises the pieces of that reference date inside it, and replaces them with today's values.
So instead of "YYYY-MM-DD", you write:
fmt.Println(time.Now().Format("2006-01-02"))
// Output: 2025-01-15
That 2006 is the year. That 01 is the month. That 02 is the day. Not because those are magic tokens — but because January 2nd, 2006 is the reference date Go was built around.
The first time I read that, I genuinely thought the documentation was trolling me.
And Then I Used the Wrong Date
Even after I understood the concept — "use a real reference date" — I still got burned. Because I thought: okay, any date will do. I need year-month-day, so I'll just write a date in that order.
I typed "2020-02-12" and felt confident.
fmt.Println(time.Now().Format("2020-02-12"))
// I expected: 2025-01-15
// I got: 10100-10-1110
The output looked like a date the way binary looks like a number — technically valid, completely unreadable.
Chaos. The numbers were all shuffled wrong.
Here's why: Go didn't see a random date. It looked at 2020 and tried to find pieces of the reference date (Mon Jan 2 15:04:05 MST 2006) inside it. It found 20 — that's the timezone offset token. It found 02 — that's the day. 12 — that's the 12-hour clock hour. None of it meant what I wanted.
The format string isn't a template where any date works. It has to be that specific date — January 2nd, 2006. That's the one Go knows. Any other date is just a string full of ambiguous numbers it'll try to interpret as tokens, with confusing results.
// This is the only date Go understands as a format
fmt.Println(time.Now().Format("2006-01-02")) // ✓
Tattoo it somewhere. 2006-01-02. Year, month, day — from the one reference date. Not your birthday. Not today's date. Not a date that "looks right." The reference date.
The "Wait, Why?!" Moment
Okay but why that specific date? Why not just use YYYY like a normal language?
This is the part that actually made me laugh once I understood it.
That reference date — 01/02 03:04:05PM '06 -0700 — is special because each component is a unique number:
- Month:
01 - Day:
02 - Hour:
03 - Minute:
04 - Second:
05 - Year:
06 - Timezone offset:
07
They count up: 1, 2, 3, 4, 5, 6, 7. Go uses this sequence so it can unambiguously look at any format string and know exactly what each piece represents — no token dictionary needed. If it sees 06 in your format string, it knows you want the two-digit year. If it sees 2006, it knows you want the four-digit year. If it sees 15, it knows you want 24-hour time (because the reference hour in 24h format is 15:04).
It's genuinely clever once it clicks. But until it clicks, it feels completely unhinged.
The Aha Moment, In Practice
Once I got it, formatting timestamps became almost fun. Here's what correct usage looks like:
package main
import (
"fmt"
"time"
)
func main() {
now := time.Now()
// Date only
fmt.Println(now.Format("2006-01-02"))
// → 2025-01-15
// Date and time
fmt.Println(now.Format("2006-01-02 15:04:05"))
// → 2025-01-15 14:32:05
// More readable
fmt.Println(now.Format("Mon, 02 Jan 2006"))
// → Wed, 15 Jan 2025
// For my Net-Cat project
fmt.Println(now.Format("[2006-01-02 15:04:05]"))
// → [2025-01-15 14:32:05]
}
The reference date is always the same: Mon Jan 2 15:04:05 MST 2006. You just rearrange and decorate it however you want your output to look.
I'd actually recommend keeping that reference date somewhere visible while you're learning Go — a sticky note, a comment at the top of your file, whatever works. You'll need to glance at it more than once.
The Takeaway
Go's time formatting is one of those things that feels bizarre at first and obvious in hindsight. Most languages teach you a set of format codes to memorise (%Y, %m, YYYY, etc.) and trust that you'll look them up when you forget them. Go took a different approach: instead of a codebook, it gave you a single example to reference.
Once you know the reference date, you don't need to memorise anything. You just ask yourself: "How would I write January 2nd, 2006 in the format I want?" — and that's your format string.
For beginners, here's the mental shift:
Don't think "what token represents the year?" — think "what does the year 2006 look like in this format?"
It's a small shift, but it changes everything.
I really wish someone had framed it that way for me on day one instead of letting me spend twenty minutes typing "YYYY" into a terminal like a confused golden retriever.
Learn from my suffering. Use the reference date. Format your timestamps.
And if your output still looks weird — check whether you're using log.Println(). Trust me on that one.
Next up: the actual Net-Cat project, and all the new ways it found to humble me. 🐹
Top comments (0)