How to build a Countdown Timer with Golang

In this tutorial, we’ll go over how to build a countdown timer in Golang. The object of the program is quite simple. We’ll specify a deadline date while running the program and then it prints out the number of days, hours, minutes and seconds from the current moment to the deadline, and it continues to do so every second until the deadline is reached.

Prerequisites

This tutorial assumes a beginner level understanding of the Go programming language. It also assumes that you have a set up your Golang environment. If you’ve never written a Go program before, this article might be a good place to start.

Getting started

Create a new directory for this project in your Go workspace. You can call it countdowntimer for example. Next, create a timer.go file in the root of your project directory. This is where all the code for this program will be written in.

$ mkdir $GOPATH/src/github.com/<your user name>/countdowntimer
$ cd $GOPATH/src/github.com/<your user name>/countdowntimer
$ touch timer.go

Set the end date

The first thing we need to do is provide a way to set the end date. This date must be a valid format that can be understood by the time.Parse() method. In this case, we’re making use of the RFC3339 format so the input string must match this format.

The ideal way to set an end date when running the program is to make use of command line arguments (a.k.a flags). The flag package makes processing CLI flags a breeze. Open up timer.go in your editor, and change it to look like this:

package main

import (
	"flag"
	"fmt"
)

func main() {
	deadline := flag.String("deadline", "", "The deadline for the countdown timer in RFC3339 format (e.g. 2019-12-25T15:00:00+01:00)")
	flag.Parse()

	fmt.Println(*deadline)
}

The flag.String() method allows us to accept a string flag. The first parameter is the flag name, the second parameter is the default value, and the third is the usage description. It returns a pointer to the memory address that stores the value of the flag.

To get the value stored in the variable that the pointer points to, you need to use the * operator as shown in the call to fmt.Println(). This is known as dereferencing. After defining all the flags, you need to call flag.Parse() to actually parse them. This method must be called before any flags are accessed by the program.

Save the file, compile the code and pass an end date when executing the binary. This should print out the value to the screen.

$ go build timer.go
$ ./timer -deadline=2019-05-03T17:00:00+01:00
2019-05-03T17:00:00+01:00

If you run the program with the -h or --help flag, a description of how the program works will be printed out. For example:

$ ./timer --help
Usage of ./timer:
  -deadline string
    The deadline for the countdown timer in RFC3339 format (e.g. 2019-12-25T15:00:00+01:00)

Here, you can see that deadline is a string flag and a description of how the flag should be used.

Make the deadline flag required

To make a flag required, we need to determine if the flag was set by comparing the value to the default value. If it’s not set, we’ll notify the user by printing a usage message of all the defined flags and exit the program immediately.

Here’s how that looks in practice:

package main

import (
	"flag"
	"fmt"
	"os"
)

func main() {
	deadline := flag.String("deadline", "", "The deadline for the countdown timer in RFC3339 format (e.g. 2019-12-25T15:00:00+01:00)")
	flag.Parse()

	if *deadline == "" {
		flag.PrintDefaults()
		os.Exit(1)
	}

	fmt.Println(*deadline)
}

Running the program without passing an end date produces the following output:

$ go build timer.go && ./timer
 -deadline string
    The deadline for the countdown timer in RFC3339 format (e.g. 2019-12-25T15:00:00+01:00)

Parse deadline as time

The next step is to verify that the deadline string passed to the program is a valid time value in the expected format. We’ll make use of the time package to parse the string into a time value as shown below:

package main

import (
	"flag"
	"fmt"
	"os"
	"time"
)

func main() {
	deadline := flag.String("deadline", "", "The deadline for the countdown timer in RFC3339 format (e.g. 2019-12-25T15:00:00+01:00)")
	flag.Parse()

	if *deadline == "" {
		flag.PrintDefaults()
		os.Exit(1)
	}

	v, err := time.Parse(time.RFC3339, *deadline)
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	fmt.Println(v)
}

If the deadline string matches a time value in the RFC3339 format, the value is stored in the v variable. Otherwise, an error is printed to the screen and the program exits.

Calculate the remaining time

Once we’ve ascertained that the deadline string is a valid time value, we need to calculate the time difference in days, hours, minutes and seconds from the current moment to the deadline.

To make that happen, we’ll define a new function that takes in a time value and calculates the difference between that time and the current time. Add the following code below the main function:

type countdown struct {
	t int
	d int
	h int
	m int
	s int
}

func getTimeRemaining(t time.Time) countdown {
	currentTime := time.Now()
	difference := t.Sub(currentTime)

	total := int(difference.Seconds())
	days := int(total / (60 * 60 * 24))
	hours := int(total / (60 * 60) % 24)
	minutes := int(total/60) % 60
	seconds := int(total % 60)

	return countdown{
		t: total,
		d: days,
		h: hours,
		m: minutes,
		s: seconds,
	}
}

Here, we’re grabbing the remaining time until the deadline in the difference variable and the total number of seconds remaining in the total variable. Once we have the total number of seconds left till the deadline, we can express it in days, hours, minutes and seconds.

For days, we divide the total number of seconds with the number of seconds in a day (86400), and discard any resulting fraction by converting to an int. We’re doing this because we want whole days not fractions of days. Similarly, the result of hours will be the number of hours left after all the days have been counted. Same for minutes and seconds.

After computing the days, hours, minutes and seconds left, we’re grouping them in new instance of the countdown struct and returning it from the function. This makes it really easy to get any of the calculated values from the function.

Continously print the time remaining until the deadline is reached

The final aspect of the program involves continously printing out the time left every second, until the deadline is reached. Update your main function to look like this:

func main() {
	deadline := flag.String("deadline", "", "The deadline for the countdown timer in RFC3339 format (e.g. 2019-12-25T15:00:00+01:00)")
	flag.Parse()

	if *deadline == "" {
		flag.PrintDefaults()
		os.Exit(1)
	}

	v, err := time.Parse(time.RFC3339, *deadline)
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	for range time.Tick(1 * time.Second) {
		timeRemaining := getTimeRemaining(v)

		if timeRemaining.t <= 0 {
			fmt.Println("Countdown reached!")
			break
		}

		fmt.Printf("Days: %d Hours: %d Minutes: %d Seconds: %d\n", timeRemaining.d, timeRemaining.h, timeRemaining.m, timeRemaining.s)
	}
}

The code inside the for loop is executed every second, and the time remaining is calculated and printed to the screen. If the remaining time gets to zero, the user is notified and the loop exits which also exits the program.

Try it out. Run the program and pass a date in the near future. It should print out the time remaining every second, until the deadline is reached.

$ go build timer.go && ./timer -deadline=2019-05-03T17:35:00+01:00
Days: 0 Hours: 0 Minutes: 5 Seconds: 8
Days: 0 Hours: 0 Minutes: 5 Seconds: 7
Days: 0 Hours: 0 Minutes: 5 Seconds: 6
Days: 0 Hours: 0 Minutes: 5 Seconds: 5
Days: 0 Hours: 0 Minutes: 5 Seconds: 4
Days: 0 Hours: 0 Minutes: 5 Seconds: 3

Add leading zeros

To refine things a bit, let’s add leading zeros to the printed output so that single digits are printed with a leading zero. To do so, change this line:

fmt.Printf("Days: %d Hours: %d Minutes: %d Seconds: %d\n", timeRemaining.d, timeRemaining.h, timeRemaining.m, timeRemaining.s)

to this:

fmt.Printf("Days: %02d Hours: %02d Minutes: %02d Seconds: %02d\n", timeRemaining.d, timeRemaining.h, timeRemaining.m, timeRemaining.s)

Here, %02d adds a zero on left for single digits. If a given value has 2 or more digits it will be skipped. For example: 8 will become 08, but 16 will remain as 16.

$ go build timer.go && ./timer -deadline=2019-05-03T17:50:00+01:00
Days: 00 Hours: 00 Minutes: 00 Seconds: 06
Days: 00 Hours: 00 Minutes: 00 Seconds: 05
Days: 00 Hours: 00 Minutes: 00 Seconds: 04
Days: 00 Hours: 00 Minutes: 00 Seconds: 03
Days: 00 Hours: 00 Minutes: 00 Seconds: 02
Days: 00 Hours: 00 Minutes: 00 Seconds: 01
Countdown reached!

That’s it! You can grab the full code here.

Wrap up

I hope you’ve enjoyed following this tutorial and learned something in the process. In the next article, I’ll explore how to build a web application with Go.

Thanks for reading. Please ask any questions in the comment section below. Also, don’t forget to subscribe to my newsletter to get notified whenever I write new articles.