Go's Time Package in a Few Minutes

Hard to believe that March was the last time anything has been published. Since then, I've graduated from school and have been very fortunate enough to land a gig where I get to write in Go everyday. As such, my experience with the language has grown, and I hope to be able to make more frequent posts of higher quality code. Thought it'd be useful to share my learning after spending a lot of time on Go's time package.  

The time package makes it really easy for you to:

  • Evaluate code runtime
  • Compare dates
  • Interpret and Describe times in different formats.

In this post, we'll look at snippets of code to see how great the time package is. If you'd like to see the examples in full scope (error handling and pretty print), view/run the single main.go file from Github.

In addition to basic time information, the time.Time struct contains information on time zone and date.

Here's a block of the common variables used in the following examples:

var(  
    // Various times that will be used by the examples.
    t1, t2, t3          time.Time

    // *time.Location representing EST and UTC time zones, respectively. 
    estLoc, utcLoc      *time.Locations
)    

the variables estLoc and utcLoc are created using the LoadLocation() function, which takes in a string and returns a valid time.Location pointer.

A time.Location is the primary means of conveying time zone information. There is a zip folder in which valid locations are loaded from, a few valid strings for LoadLocation() include est, utc, Europe/London, and America/New_York. Take a look at $GOROOT/lib/time/zoneinfo.zip for more information.


Example 1: Creating time.Time Objects in Three Different Ways:

First - The function ParseInLocation() will return a time object based on a string (format specified by the first argument) and a time zone:

t1, err = time.ParseInLocation(time.Kitchen, "6:03PM", estLoc)  
if err != nil {  
   log.Fatalln(err)
}

Another function, Parse(), which accepts a time.Format and a valid string, is similar to ParseInLocation(). The two key differences:

  • Parse will set the default time zone as UTC when it is omitted.
  • Parse will match the time zone against the Local location.

In both situations, ParseInLocation will interpret both based on the given time zone.

Second - time.Now() will return time information time.Time object based on the runtime environment.

t2 = time.Now()  

Third - the time.Date() function allows a time object to be created very specifically. The arguments, in order, describe the year, month, day, hour, minute, second, nanosecond, and location:

t3 = time.Date(2015, 12, 25, 12, 0, 0, 3, utcLoc)  

As you can see, a time.Time object can be very specific or loose upon creation. Here's the output of each time by calling and printing their String() method:

t1 = 0000-01-01 18:03:00 -0500 EST  
t2 = 2015-12-27 21:28:39.175825714 -0500 EST  
t3 = 0200-04-01 00:04:00.000000003 +0000 UTC  

Example 2: Comparing Whether Two time.Time objects are equal using the equality operator and the Equal Method:

Here we create two time.Time objects. Note that they indicate different times and time zones.

t1, err = time.ParseInLocation(time.Kitchen, "6:03PM", estLoc)  
if err != nil {  
    log.Fatalln(err)
}

t2, err = time.ParseInLocation(time.Kitchen, "11:03PM", utcLoc)  

Now, if we were to compare the two appropriately:

if t1 == t2 {  
    fmt.Printf("\nt1 and t2 are equal using `==`.")
} else {
    fmt.Printf("\nt1 and t2 are not equal using `==`.")
}

if t1.Equal(t2) {  
    fmt.Printf("\nt1 and t2 are equal using the Equal method.")
} else {
    fmt.Printf("\nt1 and t2 are not equal using the Equal method.")
}

We can observe one message saying that they are equal while the other describes otherwise:

t1 and t2 are not equal using `==`.  
t1 and t2 are equal using the Equal method.  

The equality operator takes time zone into account when comparing a time.Time object, so t1 and t2 aren't equal unless both time and time zone are equal.

The Equal method works differently because it is comparing an instance of time, which is why t1 and t2 are equal despite having different times and time zones.


Example 3: Comparing Whether One Time is Set Before or After Another:

Create two time.Time objects, t1 will be created using time.Now() while t2 will be created by calling t1's Add. The Add method takes a time.Duration as an argument and appropriately returns a time.Time. In this case, t2 is set to a full day after t1:

// Get current time and date information.
t1 = time.Now()

// Assign t2 to the current time and date information + 1 day.
t2 = t1.Add(time.Duration(24) * time.Hour)  

To understand how to manipulate a time.Duration, it's best to quote the documentation:

To count the number of units in a Duration, divide:

second := time.Second  
fmt.Print(int64(second/time.Millisecond)) // prints 1000  
To convert an integer number of units to a Duration, multiply:

seconds := 10  
fmt.Print(time.Duration(seconds)*time.Second) // prints 10s  

In this case I specified 24 hours to describe one day. It would also suffice to provide Add() with time.Duration(1) * time.Day. Now if we setup output like so:

if t1.After(t2) {  
    fmt.Printf("\nt1(%s) occurs after t2(%s)", t1.String(), t2.String())
} else {
    fmt.Printf("\nt1(%s) occurs before t2(%s)", t1.String(), t2.String())
}

The result would be:

t1(2015-12-27 21:28:39.177133929 -0500 EST) occurs before t2(2015-12-28 21:28:39.177133929 -0500 EST)  

An equivalent Before() method is also available.


Example 4: Representing Times in Different Formats:

In this example, we create a time.Time object and show how it can be represented in multiple formats. First, a date of Xmas 2015 is given to ParseInLocation. Note the use of a different format provided to ParseInLocation():

t1, err = time.ParseInLocation(time.RFC1123, "Fri, 25 Dec 2015 12:00:00 EST", estLoc)  

Now for the magic:

fmt.Printf("\nT1 has been set to Xmas day 2015 Eastern Time.")

fmt.Printf("\nT1 in RFC3339: %s", t1.Format(time.RFC3339))

fmt.Printf("\nT1 in RFC1123: %s", t1.Format(time.RFC1123))

fmt.Printf("\nT1 in custom format: %v %v %v, %v", t1.Weekday().String(), t1.Month().String(), t1.Day(), t1.Year())  

Output:

T1 has been set to Xmas day 2015 Eastern Time.  
T1 in RFC3339: 2015-12-25T12:00:00-05:00  
T1 in RFC1123: Fri, 25 Dec 2015 12:00:00 EST  
T1 in custom format: Friday December 25, 2015  

Example 5: Representing Equivalent Times in Different Time Zones

t1 is set to Xmas 2015 at noon in EST time zone, and we also create another *time.Location specific to this example which represents the time zone in London:

t1, _ = time.ParseInLocation(time.RFC1123, "Fri, 25 Dec 2015 12:00:00 EST", estLoc)

londonLoc, _ := time.LoadLocation("Europe/London")  

Now to setup the output. In the first print statement we are simply calling t1's String() method.

The interesting part of this example is the second Printf statement where we call t1's In() method. It takes a time.Location as an argument and returns a time.Time object with an equivalent time in the given time zone:

fmt.Printf("\nt1 (set to Xmas 2015 EST at noon): %s", t1.String())

fmt.Printf("\nt1 equivalent time in London: %s", t1.In(londonLoc).String())  

The output:

t1 (set to Xmas 2015 EST at noon): 2015-12-25 12:00:00 -0500 EST  
t1 equivalent time in London: 2015-12-25 17:00:00 +0000 GMT  

Example 6: Timing Code

Create two different Time objects using time.Now() before and after the code you'd like to measure the runtime for:

t1 = time.Now()  
time.Sleep(time.Duration(3) * time.Second)  
t2 = time.Now()  

To determine the difference, simply call t2's Sub() method and provide t1 as an argument. The result is a Time object that describes the difference:

tDifference := t2.Sub(t1)  
fmt.Printf("\nThe sleep was %s long", tDifference.String())  

Output:

The sleep was 3.002775829s long  

Alternative:

Disregard creating a second time completely and use the top level Since() function which takes a time.Time as an argument and returns a time.Duration of the difference:

t1 = time.Now()  
time.Sleep(time.Duration(3) * time.Second)  
fmt.Printf("\nThe sleep was %s long.", time.Since(t1).String())  

Since(t1) is shorthand for time.Now().Sub(t1)


Additional notes:
  • Times can also be parsed via binary or in JSON format (time must be in RFC 3339 format)

  • Methods exist on time.Time to extract numerical and string values such as time, date, day, and clock information. Similarly, there are methods to extract numerical values of time on a time.Duration.

  • The time package contains a timer.Timer and timer.Ticker, types which work exactly as their names descrives. There are also other related functions such as After() and Tick() which involve channels. While useful, I feel that such content is beyond the scope of a few minutes and deserves its own blog post outright.


I aimed to make this post as text minimal as possible, hopefully the code brevity was still just as effective. As always I would appreciate any feedback, especially on tweaking the examples or adding new ones. Thanks for reading!