Question

Subtract One Month from time.Time in Go with AddDate

godatetime

Question

Given a time.Time value obtained from time.Now(), how can you create another time.Time representing exactly one calendar month earlier?

time.Sub() accepts another time.Time and returns a time.Duration, but in this case the goal is to move a time backward rather than calculate the duration between two times.

Short Answer

You will learn the difference between elapsed durations and calendar-based date changes in Go. In particular, you will see why a month is not a fixed time.Duration and how time.AddDate moves a time.Time backward by one calendar month.

Concept

A time.Time represents a specific moment, such as the current date and time. A time.Duration represents a fixed amount of elapsed time measured in nanoseconds.

For fixed units, you can move a time with Add:

oneHourAgo := time.Now().Add(-time.Hour)

A calendar month is different. Months have different lengths: 28, 29, 30, or 31 days. Therefore, Go does not provide a time.Month duration constant. Writing -30 * 24 * time.Hour means exactly 30 elapsed days, not “the previous calendar month.”

Use time.Time.AddDate(years, months, days) for calendar changes:

now := time.Now()
oneMonthAgo := now.AddDate(0, -1, 0)

The arguments mean:

  • 0 years: do not change the year directly.
  • -1 months: move back one calendar month.
  • 0 days: do not change the day directly.

Use Sub when you need to measure the elapsed duration between two known times. Use Add or AddDate when you need to calculate a new time.

Mental Model

Think of time.Duration as a stopwatch. It measures a precise number of ticks, such as 90 minutes or 48 hours.

Think of AddDate as a calendar page. Turning back one calendar month means moving to the previous page, but each page can contain a different number of days.

A stopwatch can reliably subtract one hour. It cannot define “one month” because the number of hours in a month changes. A calendar operation is needed for that.

Take Quiz

Syntax and Examples

Use AddDate to add or subtract years, months, and days.

newTime := original.AddDate(years, months, days)

To subtract one month:

package main

import (
	"fmt"
	"time"
)

func main() {
	now := time.Now()
	oneMonthAgo := now.AddDate(0, -1, 0)

	fmt.Println("Now:          ", now)
	fmt.Println("One month ago:", oneMonthAgo)
}

To subtract fixed elapsed time, use Add with a negative duration:

yesterday := time.Now().Add(-24 * time.Hour)
tenMinutesAgo := time.Now().Add(-10 * time.Minute)

To calculate the difference between two moments, use Sub:

start := time.Date(2025, time.January, 1, 0, 0, 0, 0, time.UTC)
end := time.Date(2025, time.January, 3, 0, 0, 0, 0, time.UTC)

elapsed := end.Sub(start)
fmt.Println(elapsed) // 48h0m0s

Sub answers “how much time is between these moments?” Add and AddDate answer “what moment comes before or after this one?”

Step by Step Execution

Consider this fixed example:

base := time.Date(2025, time.March, 15, 14, 30, 0, 0, time.UTC)
previousMonth := base.AddDate(0, -1, 0)

Step by step:

  1. base is March 15, 2025 at 14:30 UTC.
  2. AddDate(0, -1, 0) keeps the year unchanged.
  3. It moves the month back by one, from March to February.
  4. It keeps the day and clock time when possible.
  5. previousMonth is February 15, 2025 at 14:30 UTC.
fmt.Println(previousMonth)
// 2025-02-15 14:30:00 +0000 UTC

For dates near the end of a month, understand the normalization behavior:

base := time.Date(2025, time.March, 31, 12, 0, 0, 0, time.UTC)
result := base.AddDate(0, -1, 0)

fmt.Println(result)
// 2025-03-03 12:00:00 +0000 UTC

February 31 does not exist. Go normalizes this invalid intermediate calendar date forward, producing March 3. If your business rule requires the final day of the previous month instead, implement that rule explicitly.

Real World Use Cases

Calendar-based subtraction is useful when an application works with human dates rather than a fixed number of hours.

  • Billing and subscriptions: calculate a date one month before a renewal date.
  • Reports: query records from the previous calendar month.
  • Account activity: show events since the same day in the previous month.
  • Retention policies: determine a calendar cutoff date, such as six months ago.
  • Scheduling: calculate a due date three months after an invoice date.

Fixed durations are better for elapsed-time rules:

  • Reject a request if a token expired more than 15 minutes ago.
  • Retry a failed job in 30 seconds.
  • Remove short-lived cache entries after 24 hours, if the requirement truly means 24 elapsed hours.

Choose the operation based on the requirement: calendar language such as “month,” “next Tuesday,” or “one year” generally calls for AddDate; elapsed-time language such as “30 seconds” calls for Add.

Take Quiz

Real Codebase Usage

In production code, developers commonly avoid calling time.Now() deep inside business logic. Instead, they obtain the current time once or inject it, which makes behavior consistent and easy to test.

func ReportStart(now time.Time) time.Time {
	return now.AddDate(0, -1, 0)
}

A handler or scheduled job can pass the current time:

now := time.Now()
start := ReportStart(now)

For database ranges, use a half-open interval: include the start, exclude the end. This avoids ambiguity at exact boundaries.

start := now.AddDate(0, -1, 0)
end := now

// Conceptually: created_at >= start AND created_at < end

For month-end business rules, make the policy explicit. For example, a system may define “one month before March 31” as the last day of February rather than Go's normalized date. A helper can apply that policy before constructing the target date.

Also preserve or choose a location deliberately. For business dates, calculate in the location used by that business:

loc, err := time.LoadLocation("America/New_York")
if err != nil {
	return
}

now := time.Now().In(loc)
previousMonth := now.AddDate(0, -1, 0)

Common Mistakes

Treating a month as 30 days

// Not the same as the previous calendar month.
oneMonthAgo := time.Now().Add(-30 * 24 * time.Hour)

This moves exactly 720 hours backward. It can land in the same calendar month or skip part of the previous one. Use AddDate(0, -1, 0) for a calendar month.

Using Sub in the wrong direction

// Sub returns a duration, not a new time.
result := time.Now().Sub(time.Hour)

This does not compile because Sub expects a time.Time, while time.Hour is a time.Duration. Use:

result := time.Now().Add(-time.Hour)

Expecting month-end clamping automatically

march31 := time.Date(2025, time.March, 31, 12, 0, 0, 0, time.UTC)
fmt.Println(march31.AddDate(0, -1, 0))
// 2025-03-03 12:00:00 +0000 UTC

AddDate normalizes invalid dates; it does not necessarily select the final valid day of the target month. Define and test the policy your application needs.

Ignoring time zones

A date can change differently in UTC and a local business time zone, especially around daylight-saving transitions. Use an appropriate *time.Location when the requirement is expressed in local calendar dates.

Comparing formatted strings instead of times

Avoid deciding time order by comparing display strings. Use Before, After, Equal, or Compare when comparing time.Time values.

Comparisons

NeedMethodExample
Create a time 2 hours earlierAdd with a negative durationt.Add(-2 * time.Hour)
Create a time 1 month earlierAddDatet.AddDate(0, -1, 0)
Create a time 1 year laterAddDatet.AddDate(1, 0, 0)
Measure time between two momentsSubend.Sub(start)
Check whether one time is earlierBeforea.Before(b)

Add uses fixed elapsed time. It is ideal for minutes, hours, and other duration-based requirements.

AddDate uses calendar fields. It is ideal for years, months, and days, where month lengths and local calendar rules matter.

Sub does not modify either time. It returns a time.Duration that represents the elapsed difference between two time.Time values.

Take Quiz

Cheat Sheet

// Current time
now := time.Now()

// Fixed elapsed time
oneHourAgo := now.Add(-time.Hour)
twoDaysLater := now.Add(48 * time.Hour)

// Calendar time
oneMonthAgo := now.AddDate(0, -1, 0)
threeMonthsLater := now.AddDate(0, 3, 0)
oneYearAgo := now.AddDate(-1, 0, 0)
nextWeek := now.AddDate(0, 0, 7)

// Difference between times
elapsed := later.Sub(earlier)

// Time comparisons
isEarlier := a.Before(b)
isLater := a.After(b)
isSame := a.Equal(b)

Rules to remember:

  • time.Duration is a fixed elapsed amount; it has no month unit.
  • Use a negative value with Add or AddDate to move backward.
  • AddDate(years, months, days) works with calendar fields.
  • End-of-month results may normalize into a later month when the target day does not exist.
  • Use a deliberate time zone for business calendar calculations.

FAQ

How do I subtract one month from a time.Time in Go?

Use t.AddDate(0, -1, 0). The -1 changes the month field by one month backward.

Why is there no time.Month duration constant in Go?

A duration is a fixed number of nanoseconds, but a calendar month has a variable number of days. Go uses AddDate for month and year arithmetic.

Can I use -30 * 24 * time.Hour for one month ago?

Only if your requirement explicitly means 30 elapsed days. It is not equivalent to one calendar month.

What does time.Sub return in Go?

It returns a time.Duration representing the elapsed time between two time.Time values. It does not create a shifted time.Time.

What happens when I subtract one month from March 31?

The intermediate target, February 31, is invalid. AddDate normalizes it, which can produce a date in March. Implement a custom helper if your rule requires February's final day.

Does AddDate keep the time zone and clock time?

It retains the time's location and generally preserves the clock fields, subject to local time-zone rules such as daylight-saving transitions.

Should I use UTC for monthly calculations?

Use UTC when your business rules are UTC-based. Use the relevant local location when users or business processes define dates in that local calendar.

Take Quiz

Mini Project

Description

Build a small reporting-window helper for an application that needs the current reporting period and the matching period one calendar month earlier. It demonstrates the difference between calendar arithmetic and elapsed-time arithmetic.

Goal

Create a Go program that prints the current time, the same time one calendar month ago, and the elapsed duration between them.

Requirements

Use time.Now() to obtain the current time.

Calculate one calendar month earlier with AddDate.

Calculate the elapsed difference with Sub.

Print all three values with clear labels.

Do not approximate a month with 30 days.

Take Quiz

Keep learning

Related questions

View more