What is the `zero` value for time.Time in Go? - time

In an error condition, I tried to return nil, which throws the error:
cannot use nil as type time.Time in return argument
What is the zero value for time.Time?

You should use the Time.IsZero() function instead:
func (Time) IsZero
or
func (t Time) IsZero() bool
IsZero reports whether t represents the zero time instant, January 1, year 1, 00:00:00 UTC.

Invoking an empty time.Time struct literal will return Go's zero date. Thus, for the following print statement:
fmt.Println(time.Time{})
The output is:
0001-01-01 00:00:00 +0000 UTC
For the sake of completeness, the official documentation explicitly states:
The zero value of type Time is January 1, year 1, 00:00:00.000000000 UTC.

The zero value for time.Time is 0001-01-01 00:00:00 +0000 UTC See http://play.golang.org/p/vTidOlmb9P

Tested on Go v1.18
The default value for time.Time is:
0001-01-01 00:00:00 +0000 UTC
Checking with IsZero() function is a clean way.
var zeroTime time.Time
if zeroTime.IsZero() {
// do something when zero
}

Related

Parsing ticker with datetime and dropping time elapsed

I would like to get a datetime from a ticker.C formatted string (over the network) and parse it into a Time object. ticker.C would look like 2023-01-03 17:24:13.986722973 +0100 CET m=+1.002332450. It would probably have to drop the m=+1.002332450 elapsed time as I don't see a way of keeping that in a Time object.
Also, is there a way to get a format string out of a Time object? Something like mytime.GetFormat()
The Stringer format of Time is documented here, https://pkg.go.dev/time#go1.19.4#Time.String:
String returns the time formatted using the format string
"2006-01-02 15:04:05.999999999 -0700 MST"
If the time has a monotonic clock reading, the returned string includes a final field "m=±<value>", where value is the monotonic clock reading formatted as a decimal number of seconds.
The returned string is meant for debugging; for a stable serialized representation, use t.MarshalText, t.MarshalBinary, or t.Format with an explicit format string.
Which suggests you should not try to consume that value and instead depend on a properly marshalled (or formatted) string.
Not mentioned/suggested, time.MarshalJSON is an option:
MarshalJSON implements the json.Marshaler interface. The time is a quoted string in RFC 3339 format, with sub-second precision added if present.
The sender and receiver don't have to do any special work to encode the time.Time value in JSON and then decode it again:
type wireTick struct {
Tick time.Time `json:"tick"`
}
Here's a small example of encoding and decoding the ticker on the wire with that struct, https://go.dev/play/p/Fx73q8-kVFa, which produces output like:
Sent JSON-encoded tick on wire: {"tick":"2009-11-10T23:00:01Z"}
Received tick from wire: {2009-11-10 23:00:01 +0000 UTC}
Sent JSON-encoded tick on wire: {"tick":"2009-11-10T23:00:02Z"}
Received tick from wire: {2009-11-10 23:00:02 +0000 UTC}
...
Can you modify the value being sent on the wire, or ask someone else to modify it so that it's proper?
If not, this should work:
const stringerLayout = "2006-01-02 15:04:05.999999999 -0700 MST"
timeStr := "2009-11-10 23:00:10 +0000 UTC m=+10.000000001"
tickStr := timeStr[:strings.Index(timeStr, "m=")-1]
tick, _ := time.Parse(stringerLayout, tickStr)
fmt.Printf("Received from wire: \t %q\n", timeStr)
fmt.Printf("Chopped off monotonic: \t %q\n", tickStr)
fmt.Printf("Tick is: \t\t %v\n", tick)
Received from wire: "2009-11-10 23:00:10 +0000 UTC m=+10.000000001"
Chopped off monotonic: "2009-11-10 23:00:10 +0000 UTC"
Tick is: 2009-11-10 23:00:10 +0000 UTC

Subtracting time to get age

My aim is to calculate the age of the pod by doing the subtraction of "current_time - pod_creation_time" so that I will get the age, I am getting creation time from metadata but it's in the format "2021-07-13 16:34:22 +0530 IST", so when I trying to subtract it from time.Now(), I am getting parsing error like below:
invalid operation: "t2 : " + t2 (mismatched types string and time.Time)
Anyone could please help how to have creation time "2021-07-13 16:34:22 +0530 IST" from metadata in the proper format so that I can do "time.Now - (creation time)"
I tried some workaround like below:
creatTime, err := time.Parse("2006-01-02 15:04:05 -0700 MST",
pod.ObjectMeta.CreationTimestamp.String())
and then subtracted creationTime from Current Time. It works, but I think this is not the right way.
There's a type mismatch as time.Now() return the current time stored in the type time.Time whereas 2021-07-13 16:34:22 +0530 IST is a string. You can perform the required subtraction operation on mismatched types i.e., time.Time and string.
You have to parse the string by specifying the layout. I'd recommend reading the time package's doc.
I've explained every operation in the sample code below; I hope it helps. If you understand this, you can also then look at helper functions like time.Since that can help you write the same program in fewer lines.
package main
import (
"fmt"
"time"
)
func main() {
// K8s timestamp
t := "2021-07-13 16:34:22 +0530 IST"
// Format of K8s timestamp
format := "2006-01-02 15:04:05 -0700 MST" // Mon Jan 2 15:04:05 -0700 MST 2006
// Parse the timestamp so that it's stored in time.Time
cur, err := time.Parse(format, t)
if err != nil {
panic(err)
}
// Current time
now := time.Now()
// As both are of type time.Time, it's subtractable
dur := now.Sub(cur)
// Print duration
fmt.Println(dur)
// Print duration (in seconds)
fmt.Println(dur.Seconds())
}
Also, I'd like you to learn how to write questions on StackOverflow. The formatting of your question is pretty bad. When seeking good solutions; it is the OP's duty to post the question correctly first so that everybody could understand it and then expect answers.
Read: https://stackoverflow.com/help/how-to-ask

Why is the user defined time's location nil

I'm using go 1.13 and I have a user defined type of type time.Time, and when creating that value with a given location of UTC, the loc attribute is still nil (having a nil loc causes panics in certain time functions, so this is not acceptable). Playground here.
type CustomTime time.Time
func main() {
t := CustomTime(time.Date(2020, time.July, 23, 1, 0, 0, 0, time.UTC))
fmt.Printf("%+v",t) // prints {wall:0 ext:63731062800 loc:<nil>}
}
FYI: background info, I'm using this custom time to implement Scan() for my database handler, and when I compare a custom time value defined above (with nil location to the value from the db (non-nil location), my tests are failing due to the comparison failing. Any help or pointers in the right direction would be greatly appreciated.
If you look at the doc, time.Time is of type
type Time struct {
//...
wall uint64
ext int64
// loc specifies the Location that should be used to
// determine the minute, hour, month, day, and year
// that correspond to this Time.
// The nil location means UTC.
// All UTC times are represented with loc==nil, never loc==&utcLoc.
loc *Location
}
nil loc actually means UTC. You can verify the same by printing the equality
fmt.Println(time.UTC == time.Time(t).Location())
// Output: true
You see a nil when you print t because you are literally printing the struct Time without using its default Stringer as you have wrapped it with a Custom Type i.e. CustomTime. Hence the loc field will be nil.
fmt.Printf("%+v", time.Time(t))
// This will print UTC for the location.
If you want to use CustomTime everywhere, instead of creating a type alias you can embed time.Time in a struct so that CustomTime can behave like time.Time
type CustomTime struct {
time.Time
}
func main() {
t := CustomTime{time.Date(2020, time.July, 23, 1, 0, 0, 0, time.UTC)}
fmt.Printf("%+v", t) // Prints: 2020-07-23 01:00:00 +0000 UTC
}

How to read package documentation

I am trying to understand how to use/call libraries & functions of Golang by reading the official documentation but couldn't fully comprehend.
Below are some examples that I hope to get advise from the experts here in SO
Example 1: time
From the documentation:
type Time
func Date(year int, month Month, day, hour, min, sec, nsec int, loc *Location) Time
func Now() Time
I interpret the above as type Time has method Now which does not take in any input and returns a Time type.
thus,
var t time.Time
fmt.Println(t)
yields 0001-01-01 00:00:00 +0000 UTC So. t is a valid time.Time type variable.
Question 1:
But why is t.Now() throwing an error t.Now undefined (type time.Time has no field or method Now)?
Question 2:
Interestingly, time.Now() returns the value desired. Does that mean Now() is not a method of type Time?
var t time.Time declares a variable of type time.Time with the zero value for the type.
func Now() Time: Now() is a function with no parameters which returns type time.Time
func (t Time) Month() Month: Month() is a method on the receiver t type time.Time with no parameters which returns type time.Month.
For example,
package main
import (
"fmt"
"time"
)
func main() {
var t time.Time
fmt.Println(t)
t = time.Now()
fmt.Println(t)
m := t.Month()
fmt.Println(m)
}
Playground: https://play.golang.org/p/Ume5kxDAe05
Output:
0001-01-01 00:00:00 +0000 UTC
2009-11-10 23:00:00 +0000 UTC m=+0.000000001
November
Note: In the playground the time begins at 2009-11-10 23:00:00 UTC. This makes it easier to cache programs by giving them deterministic output.
Take A Tour of Go.
See The Go Programming Language Specification
I think what you are really asking here is: why is the package documentation laid out the way it is? That is, for this specific case, in this specific package documentation, we see:
type Time
func Date(year int, month Month, day, hour, min, sec, nsec int, loc *Location) Time
func Now() Time
where the type Time line appears by itself, then immediately underneath it, several func declarations appear that return a value of type Time.
The reason these are indented two characters is to indicate that these functions return a value of type Time. The reason that they appear under the type Time line is that these functions return a value of type Time.
In this case, both reasons add up to the same thing—they're redundant. That's OK though! It's just a function of the fact that the Go documentation generator is a program that obeys these sort and indent rules. Nothing here implies that the two functions are receiver functions.
Consider another example from the same package documentation. Somewhat earlier, we see:
type Duration
func ParseDuration(s string) (Duration, error)
func Since(t Time) Duration
func Until(t Time) Duration
Here, this tells us that all three of these functions return a Duration—though the first one returns both a Duration and an error. The ParseDuration function is an ordinary function. It is the functions Since and Until that are receiver functions.1 They take a receiver argument of type Time (and no other arguments) and return a value of type Duration.
In some other design, it might make sense to sort the Since and Until functions underneath the type name Time, since these are receiver functions of type Time. But the package documentation sorts (and groups) by return type, not receiver or argument type. That's all there really is to it here.
1You can—and the spec does—call these methods if you like.

Idiomatic way to represent optional time.Time in a struct

I've read both Optional Parameters? and Golang pass nil as optional argument to a function?
And still wonder if my case is more specific.
What I've got is:
type Periodical struct {
Interval *interval.Interval
StartsAt time.Time
EndsAt time.Time
}
to represent periodical event which has a start date and may or may not have an end date (periodical event runs for indefinite amount of time).
eachYear := Periodical{
interval.Years(1),
StartsAt: time.Parse("2 Jan 2006", "1 Jan 1970")}
Will throw
periodical/periodical.go:31:39: cannot use nil as type time.Time in field value
Which is understood, - I didn't specify EndsAt time.Time.
But what do I really do there then?
Am I forced to have a special flag to neglect EndsAt like so?
type Periodical struct {
Interval *interval.Interval
StartsAt time.Time
EndsAt time.Time
isIndefinite bool // This looks ugly already
}
and then if I want Yearly / Anually I do something like
eachYear := Periodical{
interval.Years(1),
time.Parse("2 Jan 2006", "1 Jan 1970"),
time.Parse("2 Jan 2006", "1 Jan 1970"),
isIndefinite: true}
Although, I can then account for this flag in business logic, but this EndsAt set to the same (or any other) date looks kind of dull.
I also define a method on periodical package which allows to have a shorthand periodical event like so:
func Monthly(s, e time.Time) Periodical {
return Periodical{StartsAt: s, EndsAt: e, Interval: interval.Months(1)}
}
What do I do to omit end (the second param)? Am I forced to either have separate method for that or do something that looks a bit funky and lacks readability:
func Monthly(s time.Time, end ...time.Time) Periodical {
if len(end) == 1 {
return Periodical{
StartsAt: s,
EndsAt: end[0],
Interval: interval.Months(1),
isIndefinite: false}
} else if len(end) > 1 {
panic("Multiple end dates are not allowed, don't know what to do with those")
} else {
return Periodical{
StartsAt: s,
EndsAt: time.Now(),
Interval: interval.Months(1),
isIndefinite: true}
}
}
Although it does the trick, it looks ugly, isn't it? My concise one-liner is now scattered along several lines of code.
So, that's why I wonder, what's the go's idiomatic way of achieving what I'm trying to do?
time.Time is a struct. Its zero value–although being a valid time value–is like never used. You can utilize the zero value time to signal the missing or undefined time value. There is even a Time.IsZero() method which tells you if a time.Time value is the zero value.
Note that the zero value time is not Jan 1, 1970 like in some other languages, but Jan 1, year 1, as you can see on the Go Playground. This is also documented at time.Time:
The zero value of type Time is January 1, year 1, 00:00:00.000000000 UTC. As this time is unlikely to come up in practice, the IsZero method gives a simple way of detecting a time that has not been initialized explicitly.
Also, when creating a Periodical value, use keyed composite literal, and you can omit fields which you don't want to set, thus leaving them at their zero value:
eachYear := Periodical{
Interval: interval.Years(1),
StartsAt: time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC),
}
Note that you can't use time.Parse() in this composite literal as that returns 2 values (a time.Time and an error). Either use time.Date() as in the above example, or create the time value prior (handle error), and just use the time value.
To tell if EndsAt is specified:
if eachYear.EndsAt.IsZero() {
fmt.Println("EndsAt is missing")
}
Should you need to zero an already set (non-zero) time value, you may use the time.Time{} composite literal:
eachYear.StartsAt = time.Time{}
Also note though that when marshaling a time.Time value, even if it's the zero value (since it is a valid time value), it will be sent even if you use the omitempty option. In those cases you must use a *time.Time pointer or write custom marshalers. For details, see Golang JSON omitempty With time.Time Field.

Resources