Any programming language using dates and time has some inherent complexity associated with it. Handling these data types is not like using any other types because of the dependency or the references they follow. For example, Go follows the type for time, which begins with 1st January 1970 at 00 hour, 00 minutes and 00 second UTC. This is known as UNIX epoch. To better understand this data type, let’s dive into the intricacies of Unix epoch before learning how to work with the associated Go APIs used for working with dates and time.
What is the UNIX epoch in Go Programming?
The Go programming language follows the UNIX epoch, beginning with January 1, 1970 at 00:00:00 UTC. UNIX keeps track of the time by counting the number of seconds since this special day. The counter for the number of seconds elapsed is stored in a 32-bit integer and the maximum number that can be stored in a 32-bit integer is 2,147,483,647. Since, in computing, numbers have a binary representation (binary encoding), this maximum value is represented with 31, 1’s. Note also that, since it is an integer, it can be both negative and positive. Therefore, half of the encoded numbers will be positive and the other half will be negative numbers. This is made possible by representing the first binary digit as sign-bit, which simply means numbers beginning with 0 are positive and a number beginning with 1 is negative. But the question is: why do we need negative numbers if we simply want to count forward the number of seconds elapsed since the special day?
Reading: Understanding Mathematical Operators in Go
Why do we need signed integers?
Signed integers are used because a date before the special day (Jan 1, 1970) is represented by a negative integer, and the date after is represented with a positive integer. Therefore, a second count, such as 100, means 100 seconds before UNIX epoch and +100 would mean a 100-second count after UNIX epoch. Think of the number line: positive moves forward and negative moves back from 0 (in this case from 1-1-1970, 00:00:00 UTC).
The Millennium Bug
When we decide to represent time with a 32-bit integer, the maximum value – or the maximum second count – would be 2147483647. Now, if we translate that into years, months and days in a human understandable form, the date we get is 19th Jan 2038 at around 3:14:08 am. So, this is the maximum date we get with a 32-bit integer.
Now, what happens to any system that uses such a clock – where one second after the date and time represented by the maximum 32-bit value goes from a maximum sign bit integer to a bit representation and where the first sign bit 1 is followed by 31 0s? The clock seems to reset back to a number which, when converted to a date format, becomes 13th December Friday 1901. This simply means that the next tick after 19th Jan 2038, 03:14:08 will be 13th Dec, 1901. This is the mess behind the infamous “Millennium bug”.
The Solution to the Millennium Bug
Developers may recall the Y2K bug – although a problem, no systems crashed due to this, despite all of the apocalyptic expectations. Despite the lack of system crashes, the problem is quite real and was, fortunately, solved successfully. This problem is going to pop up once again with the Unix epoch, but we have some time until 2038 to fix it. The bug can be patched up temporarily by using a bigger number. At some point in time, this problem will pop up again. A solution is to use 64-bit integers rather than 32-bit.
Even still, a 64-bit – or even a 128-bit number – is but a temporary solution for the problem and only serves to buy us some time until the next “Millennium bug”. Just to give you an idea, this 64-bit number will run out on 4th Dec 3:30pm, year 292277026596 AD. So we will get some hits before the next “Millennium bug”!
Now that we have covered what the Unix epoch is, why Go developers need signed integers, what causes “Millennium bugs”, and their temporary solutions, let’s go back to learning about the date and time API in Go.
Reading: How to Handle Errors in Go
Working with The time Package in Go
Although Go uses the UNIX notion of time, for all practical purposes the time interval used by the Go API is in nanoseconds. The difference between time and time interval is that time represents a specific point in the epoch and depends on things like time zone and it makes no sense in isolation. Time interval, on the other hand, is a quantity or a number that can be used separately.
The time package includes a type called Duration, which represents the elapsed time between two instants as an int64 nanosecond count. The largest representation count is approximately 290 years. Note, however, that the Time type represents an instant (or fixed point) in time with nanosecond precision.
The Time Type in Go
The following code snippet shows how to find the number of seconds elapsed since the Epoch using the Go programming language:
fmt.Printf(“n%d seconds elapsed since UNIX epoch (1970, Jan 1; 00:00:00)”, time.Now().Unix()) fmt.Printf(“n%d nanoseconds elapsed since UNIX epoch (1970, Jan 1; 00:00:00)”, time.Now().UnixNano())
Note that Now() is a convenient function provided by the time package. This function returns a structure type called Time, which not only encapsulates the time but also the timezone. The time structure provides a number of methods to be used with respect to the time data type. Here is a quick example showing how to find the approximate age of a person, given a birth date, using Golang:
package main import ( “fmt” “time” ) func main() { bdate := time.Date(1980, time.Month(2), 16, 0, 0, 0, 0, time.UTC) cdate := time .Now() y, m, d := calcAge(bdate, cdate) fmt.Printf(“%d years, %d months %d days”, y, m, d) } func calcAge(bdate, cdate time.Time ) (int, time.Month, int) { if cdate.Year() < bdate.Year() { return -1, -1, -1 } by, bm, bd := bdate.Date() cy, cm, cd := cdate.Date() if cd < bd { cd += 30 cm-- } if cm < bm { cm += 12 cy-- } return cy - by, cm - bm, cd - bd }
Running this code in your integrated development environment (IDE) or code editor will result in the following output:
41 years, 11 months 21 days
Reading: Methods in Go Explained
Formatting and Parsing Dates in Go
The Format() method returns a text representation of time data. Given a layout, the date and time can be formatted according to the need. For example, it is possible to provide a pattern when converting time to a string value, as shown here:
dt := time.Now() fmt.Printf(“n%s”, dt.Format(“2006:01:01”))
The above code prints the date output using a colon (:) as the separator. If we want to print a 12-hour time format we would write:
fmt.Printf(“n%s”, dt.Format(time.Kitchen))
The Go programming language provides a number of constants that can be used as layouts when formatting time values. The constant, time. Kitchen is one of them. Check out the official Go time package documentation for other such constants.
Now, if Go developers want to parse a date from a string value, they can use the Parse() function. This function helps in constructing a time structure from a string according to a specified format. Here is how that looks in code:
str := “Jan 2, 2006 at 3:04pm (MST)” t, err := time.Parse(str, “Feb 4, 2014 at 6:05pm (PST)”) if err != nil { fmt.Println (err) } fmt.Println
Finding Time Difference with Go
If we have two time values, the simplest way to find the difference between them is by converting them to UNIX times and then performing the calculation. Suppose we want to find the duration of time taken by a function execution. We can capture the time snapshot once at the beginning of the function call and then again after the function returns to the caller. The time difference will show the duration it took to execute the function. Here is an example of how to code this in Go:
package main import ( “fmt” “time” ) func main() { t1 := time.Now() fmt.Println(getFibonacciNum(600)) t2 := time.Now() fmt.Printf(“n%d seconds difference.”, t2.Unix()-t1.Unix()) fmt.Printf(“n%s difference”, t2.Sub(t1)) } func getFibonacciNum(max int) int { a := 0 b := 1 var c int if max < 1 { return -999 } if max == 1 { return 0 } if max == 2 { return 1 } for i := 2; i < max; i++ { c = a + b a = b b = c } return c }
Here is the output of the above code:
0 second difference. 17.798µs difference
Final Thoughts on Working with Dates and Time in Go
The main problem with working with time values is that they are quite complex to maintain. As mentioned in this Go programming tutorial, the problem with the UNIX Epoch is the limitations on size for 32-bit integers, which results in “Millennium bugs.” Further, there are time zones and different types of calendars used all over the world that further complicate the issue. The Go API provides ample functions to work with dates and times in the Golang language. Here, we demonstrated a few of them. Stay tuned for more!
read more Go and Golang programming tutorials.