Go version

go1.20.8

Output of go env in your module/workspace:

GO111MODULE="on"
GOARCH="arm64"
GOBIN=""
GOCACHE="/Library/Caches/go-build"
GOENV="/Library/Application Support/go/env"
GOEXE=""
GOEXPERIMENT=""
GOFLAGS=""
GOHOSTARCH="arm64"
GOHOSTOS="darwin"
GOINSECURE=""
GOMODCACHE="/go/pkg/mod"
GOOS="darwin"
GOPATH="/go"
GOPROXY="https://goproxy.cn,direct"
GOROOT="/go/go1.20.8"
GOSUMDB="off"
GOTMPDIR=""
GOTOOLDIR="/go/go1.20.8/pkg/tool/darwin_arm64"
GOVCS=""
GOVERSION="go1.20.8"
GCCGO="gccgo"
AR="ar"
CC="clang"
CXX="clang++"
CGO_ENABLED="1"
GOWORK=""
CGO_CFLAGS="-O2 -g"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-O2 -g"
CGO_FFLAGS="-O2 -g"
CGO_LDFLAGS="-O2 -g"
PKG_CONFIG="pkg-config"

What did you do?

There is a bug in the time library when adding or subtracting months. When the time is 15:00:00 on July 31, 2025 and 15:00:00 on August 31, 2025, the output is July and October when t. AddDate (0, -1,0) and t. AddDate (0, 1,0) are executed respectively

What did you see happen?

package main

import ( "fmt" "time" )

func main() { t, _ := time.ParseInLocation("2006-01-02 15:04:05", "2025-07-31 15:00:00", time.Local) fmt.Println(t.AddDate(0, -1, 0).String()) // 2025-07-01 15:00:00 +0800 t, _ = time.ParseInLocation("2006-01-02 15:04:05", "2025-08-31 15:00:00", time.Local) fmt.Println(t.AddDate(0, 1, 0).String()) // 2025-10-01 15:00:00 +0800 }

What did you expect to see?

2025-06-30 15:00:00 +0800 and 2025-09-30 15:00:00 +0800

Comment From: gabyhelp

Related Issues

(Emoji vote if this was helpful or unhelpful; more detailed feedback welcome in this discussion.)

Comment From: adonovan

The problem is that AddDate(+/- 1 month) simply increments/decrements the month column then normalizes, so:

  • 31 Jul - 1 month = "31 Jun" = 1 Jul
  • 31 Aug + 1 month = "31 Sep" = 1 Oct

Months are not of the same length, so there will always be an edge case trying to map from the last day to "the same day" of the next or previous month. This is a dup of several previous issues, as enumerated by gabyhelp (e.g. https://github.com/golang/go/issues/31145). We cannot change the behavior of AddDate at this point, even if we wanted to (though the previous issues suggest we do not). But this is a recurring source of surprise that should be better documented, and if there is one clear alternative algorithm that many users expect, we should provide it and refer to it.

Comment From: ALTree

It seems to be documented?

AddDate normalizes its result in the same way that Date does, so, for example, adding one month to October 31 yields December 1, the normalized form for November 31.

Comment From: adonovan

It is documented, yet users are perennially confused enough to report issues. Perhaps it should provide more advice on how to safely work with month strides.

Comment From: ianlancetaylor

The existing documentation seems very clear to me. It explicitly says

AddDate normalizes its result in the same way that Date does, so, for example, adding one month to October 31 yields December 1, the normalized form for November 31.

which is the exact case that the original poster is misunderstanding. Is there really anything we can do to make this more clear?

Comment From: seankhliao

I think this is more of a case where we provided an API which we shouldn't have, and users don't read the docs before trying to use something.