Question

What Is the Zero Value of time.Time in Go?

gotimenull

Question

In Go, if a function returns a time.Time, I cannot return nil during an error case because Go reports:

cannot use nil as type time.Time in return argument

What is the zero value of time.Time, and how should it be used when a valid time is not available?

Short Answer

By the end of this page, you will understand why time.Time cannot be nil, what its zero value is, how to return it from functions, and how to check whether a time.Time has been set in real Go programs.

Concept

In Go, every type has a zero value. This is the default value a variable gets when you declare it without explicitly assigning anything.

For example:

  • int → 0
  • string → ""
  • bool → false
  • pointers, slices, maps, functions, interfaces → nil

But time.Time is not a pointer. It is a struct type from Go's time package. Structs cannot be nil unless you use a pointer to them, such as *time.Time.

So the zero value of time.Time is simply:

time.Time{}

This represents the earliest representable time in Go's time.Time type:

0001-01-01 00:00:00 +0000 UTC

This matters because many Go APIs return zero values together with an error when something goes wrong. If your function returns (time.Time, error), then on failure you usually return:

return time.Time{}, err

This follows a common Go pattern:

  • return the zero value for the main result
  • return a non-nil error

Go code often uses time.Time{} or t.IsZero() to represent “no meaningful time available yet.”

Mental Model

Think of a time.Time value like a printed form with date fields on it.

  • A pointer can be nil, which is like saying: “there is no form at all.”
  • A struct value like time.Time always exists as a full form.
  • Its zero value is like a blank default form that has not been filled in with a real date.

So when you have a plain time.Time, you cannot say “no form” with nil. You can only use its default blank value:

time.Time{}

If you really need the idea of “no time at all,” then you can use a pointer:

var t *time.Time = nil

But in normal Go code, returning time.Time{} plus an error is usually the simplest and most idiomatic choice.

Take Quiz

Syntax and Examples

The core syntax is straightforward.

Zero value of time.Time

var t time.Time
fmt.Println(t)

Because t was declared but not assigned, it gets the zero value automatically.

Explicit zero value

zero := time.Time{}
fmt.Println(zero)

This creates the same zero value explicitly.

Returning zero value from a function

package main

import (
	"errors"
	"time"
)

func getTimestamp(ok bool) (time.Time, error) {
	if !ok {
		return time.Time{}, errors.New("timestamp not available")
	}

	return time.Now(), nil
}

Here:

  • time.Now() is returned when successful
  • time.Time{} is returned when there is an error

Checking whether a time is zero

package main

import (
	"fmt"
	"time"
)

func main() {
	var t time.Time

	fmt.Println(t.IsZero()) // true
	fmt.Println(time.Now().IsZero()) // false
}

The IsZero() method is the safest and clearest way to check whether a time.Time has its zero value.

Step by Step Execution

Consider this example:

package main

import (
	"errors"
	"fmt"
	"time"
)

func parseDeadline(input string) (time.Time, error) {
	if input == "" {
		return time.Time{}, errors.New("empty deadline")
	}

	return time.Parse("2006-01-02", input)
}

func main() {
	deadline, err := parseDeadline("")
	fmt.Println("deadline:", deadline)
	fmt.Println("is zero:", deadline.IsZero())
	fmt.Println("error:", err)
}

What happens step by step

  1. main() calls parseDeadline("").
  2. Inside parseDeadline, the input is an empty string.
  3. The condition input == "" is true.
  4. The function returns:
    • time.Time{} as the zero time.Time
    • an error created with errors.New(...)
  5. Back in main(), deadline now holds the zero time value.
  6. Printing deadline shows something like:
0001-01-01 00:00:00 +0000 UTC
  1. deadline.IsZero() returns true.
  2. The error contains the message empty deadline.

This shows the standard Go pattern: return the zero value of the main type when the operation fails.

Real World Use Cases

time.Time zero values appear in many practical Go programs.

1. Function results with errors

A function that fetches a timestamp from a database or API may return:

return time.Time{}, err

if the lookup fails.

2. Optional timestamps

You may have fields such as:

  • CreatedAt
  • UpdatedAt
  • DeletedAt
  • PublishedAt

A zero time.Time can mean the timestamp has not been set yet.

3. Form or config validation

If a date was not provided, a zero time.Time may represent “missing input” until validation decides whether that is allowed.

4. Scheduling systems

A job runner might use zero time to mean:

  • never run
  • not scheduled yet
  • retry time not calculated yet

5. Parsing and transformation pipelines

When converting raw strings into typed data, zero time may temporarily represent an unset or failed value until errors are handled.

Take Quiz

Real Codebase Usage

In real Go codebases, developers usually use time.Time in a few common patterns.

Guard clause with zero return

func startTimeFromConfig(cfg string) (time.Time, error) {
	if cfg == "" {
		return time.Time{}, errors.New("missing config value")
	}
	return time.Parse(time.RFC3339, cfg)
}

This is a classic guard clause: fail early, return the zero value, and report the error.

Validation with IsZero()

func canPublish(publishedAt time.Time) bool {
	return !publishedAt.IsZero()
}

This is clearer than comparing formatted strings or manually checking fields.

Optional field design

Sometimes developers choose between:

  • time.Time when a value should always exist eventually
  • *time.Time when nil should explicitly mean “missing” or “not set”

For example:

type User struct {
	LastLogin *time.Time
}

Here, nil can mean the user has never logged in.

Error handling pattern

Functions often look like this:

func loadExpiry() (time.Time, error) {
	t, err := time.Parse(time.RFC3339, "bad-input")
	if err != nil {
		return time.Time{}, err
	}
	return t, nil
}

That pattern is common throughout Go: return the type's zero value on error.

Struct fields and defaults

In configuration structs or model structs, a zero time.Time is often used as the default state until a real timestamp is assigned.

Common Mistakes

Here are common mistakes beginners make with time.Time.

Mistake 1: Returning nil for a non-pointer type

Broken code:

func getTime() (time.Time, error) {
	return nil, errors.New("failed")
}

Why it fails:

  • time.Time is a struct, not a pointer
  • only some types can be nil

Correct version:

func getTime() (time.Time, error) {
	return time.Time{}, errors.New("failed")
}

Mistake 2: Comparing to nil

Broken code:

var t time.Time
if t == nil {
	fmt.Println("unset")
}

Why it fails:

  • a time.Time value can never be nil

Correct version:

if t.IsZero() {
	fmt.Println("unset")
}

Mistake 3: Using string output to detect zero time

Less reliable approach:

if t.String() == "0001-01-01 00:00:00 +0000 UTC" {
	// avoid this
}

Better approach:

if t.IsZero() {
	// correct
}

Mistake 4: Using time.Time when *time.Time is more appropriate

Sometimes you need three states:

  • no value at all
  • value exists
  • error happened elsewhere

In that case, a pointer may better represent optional data.

type Event struct {
	StartedAt *time.Time
}

Use this only when nil has clear meaning in your design.

Mistake 5: Ignoring the error and trusting the time value

Broken pattern:

t, _ := time.Parse(time.RFC3339, "not-a-date")
fmt.Println(t)

If parsing fails, t will be the zero value and the real problem is hidden.

Better:

t, err := time.Parse(time.RFC3339, "not-a-date")
if err != nil {
	fmt.Println("parse failed:", err)
	return
}
fmt.Println(t)

Comparisons

Here is how time.Time compares with related choices.

ConceptCan be nil?Zero/default valueBest use
time.TimeNotime.Time{}Regular timestamps that always have a value type
*time.TimeYesnilOptional timestamps where absence matters
string date like "2024-01-01"No""Raw input or display text, not ideal for date logic

time.Time vs *time.Time

var a time.Time   // zero value is time.Time{}
var b *time.Time  // zero value is nil

Choose time.Time when:

  • you want simple value semantics
  • a zero value is acceptable as a default
  • you prefer idiomatic Go function returns

Choose *time.Time when:

  • nil has meaningful business value
  • you need to distinguish “unset” from “set to a real time”
  • you are working with optional JSON/database fields

Zero value vs current time

These are very different:

var t time.Time      // zero time
now := time.Now()    // current time

A zero value does not mean “now.” It means “default uninitialized time value.”

Take Quiz

Cheat Sheet

// Zero value of time.Time
var t time.Time

// Explicit zero value
zero := time.Time{}

// Return zero value on error
return time.Time{}, err

// Check whether a time is zero
if t.IsZero() {
    // t has not been set
}

Rules to remember

  • time.Time is a struct, so it cannot be nil
  • *time.Time is a pointer, so it can be nil
  • The zero value of time.Time is time.Time{}
  • The printed zero time is usually 0001-01-01 00:00:00 +0000 UTC
  • Use IsZero() to test for the zero value
  • In (time.Time, error) functions, return time.Time{}, err on failure

Quick decision guide

  • Need a normal timestamp value? Use time.Time
  • Need to represent missing time with nil? Use *time.Time

FAQ

Why can't I return nil for time.Time in Go?

Because time.Time is a struct value, not a pointer. Only certain types such as pointers, slices, maps, interfaces, channels, and functions can be nil.

What is the zero value of time.Time?

The zero value is:

time.Time{}

It prints as 0001-01-01 00:00:00 +0000 UTC.

How do I check if a time.Time is unset?

Use:

t.IsZero()

This is the standard and most readable approach.

Should I use time.Time{} or time.Now() as a default?

Use time.Time{} only when you mean “no real time set yet.” Use time.Now() when you want the current time.

When should I use *time.Time instead of time.Time?

Use *time.Time when nil should represent a truly missing optional value.

Is zero time.Time considered valid?

It is valid as a Go value, but it may not be meaningful in your application's business logic. You should decide whether zero time is allowed in your domain.

What should I return from a function with signature (time.Time, error) when it fails?

Return:

return time.Time{}, err

This matches standard Go error-handling conventions.

Take Quiz

Mini Project

Description

Build a small Go program that simulates loading an event start time from user input. This project demonstrates how to return a zero time.Time on failure, how to detect unset times with IsZero(), and how to handle successful and failed parsing clearly.

Goal

Create a program that parses an optional date string and reports whether it received a valid time or a zero time.Time.

Requirements

  • Write a function that accepts a date string and returns (time.Time, error).
  • Return time.Time{} when the input is empty or invalid.
  • Parse valid input using the layout 2006-01-02.
  • In main, test both a valid date and an empty string.
  • Print whether each returned time is zero using IsZero().
Take Quiz

Keep learning

Related questions

View more