golang / go

The Go programming language
https://go.dev
BSD 3-Clause "New" or "Revised" License
122.12k stars 17.45k forks source link

proposal: time: add IsUnixEpoch method to test whether a time is the Unix epoch #46974

Closed joshbradshaw11 closed 3 years ago

joshbradshaw11 commented 3 years ago
$ go version

go version go1.15.6 darwin/amd64

Does this issue reproduce with the latest release?

Yes

What did you do?

This is just an opinion, but I think that the the time library should provide a simpler method for getting the Epoch time.

I was surprised by this:

t := time.Unix(int64(0), 0)
log.Println("Time is ", t.String())
log.Println("Is time zero: ", t.IsZero())
log.Println("Is time epoch: ", t == time.Unix(int64(0), 0))
Time is  1969-12-31 19:00:00 -0500 EST
Is time zero:  false
Is time epoch:  true

Upon further inspection I realized that I needed to do time.Unix(0, 0).UTC() to get the epoch value because time.Unix() is timezone aware. Given that the API already has a time.isZero() method, I think there should be a method for fetching the environment's epoch value directly, without using a parsing method or a timezone aware method.

ianlancetaylor commented 3 years ago

Note that the time package's epoch is simply 1-1-1 00:00:00 UTC which is not coincidentally the zero value of time.Time. You are talking about the Unix epoch, which the time package supports via the time.Time methods Unix, UnixNano, UnixMilli, and UnixMicro (the latter two new in 1.17) and the Unix, UnixMilli, and UnixMicro functions (again the latter two are new in 1.17). Other than those functions and methods the Unix epoch is unimportant to the time package.

You say an Epoch method but I'm guessing that in Go terminology you mean an Epoch function. Specifically, you suggest

// UnixEpoch returns the start of the Unix epoch in UTC.
func UnixEpoch() time.Time {
    return time.Unix(0, 0).UTC()
}

Is that correct?

If that is correct, it's not clear to me that that is useful enough by itself. How often does this come up? When do you need the Unix epoch value as a time.Time?

joshbradshaw11 commented 3 years ago

Note that the time package's epoch is simply 1-1-1 00:00:00 UTC which is not coincidentally the zero value of time.Time. You are talking about the Unix epoch, which the time package supports via the time.Time methods Unix, UnixNano, UnixMilli, and UnixMicro (the latter two new in 1.17) and the Unix, UnixMilli, and UnixMicro functions (again the latter two are new in 1.17). Other than those functions and methods the Unix epoch is unimportant to the time package.

You say an Epoch method but I'm guessing that in Go terminology you mean an Epoch function. Specifically, you suggest

// UnixEpoch returns the start of the Unix epoch in UTC.
func UnixEpoch() time.Time {
    return time.Unix(0, 0).UTC()
}

Is that correct?

If that is correct, it's not clear to me that that is useful enough by itself. How often does this come up? When do you need the Unix epoch value as a time.Time?

I encountered this problem while writing an embedded application for a 3D printer. We manufacture separate resin cartridges for these 3D printers, and these cartridges have little EEPROMs onboard that store usage statistics. One of the statistics we need to store is the date that the cartridge was first used, because the material has a short shelf life. We store this value in the units UnixEpoch_s. We use 0 / unix epoch as the sentinel value to tell us that this field is uninitialized.

I made a bug in this case by using time.IsZero() as opposed to a comparison with time.Unix(0, 0).UTC().

Given that time.IsZero already exists, I think that it would be very reasonable to provide an time.isEpoch function as a convenience for people who write software that interoperates with other systems that do use the unix epoch as the zero time value. I think that listing isZero and isUnixEpoch alongside each other in the library documentation will help draw people's attention to this particular design decision in go to use 1-1-1 00:00:00 UTC, which differs from many other business oriented programming languages.

ianlancetaylor commented 3 years ago

Thanks for the example. Honestly that seems kind of specific to me. I don't see why many people would use the suggested IsUnixEpoch method, and of course it is trivial to provide this outside the standard library (https://golang.org/doc/faq#x_in_std). I agree that this is something that people using the time package have to learn, but it is already clearly documented, and once you learn it you will remember it. Even the documentation for IsZero refers to specifically to "year 1".

joshbradshaw11 commented 3 years ago

My final comment is that the IsZero method never should have existed because time is an affine space type. There's a reason every other language uses the word epoch

ianlancetaylor commented 3 years ago

The IsZero method exists because all Go types must have a zero value, and it can be useful to test whether a time.Time value was never initialized. Perhaps it was a mistake, but it's too late now.