Golang Project Structure

Tips and tricks for writing and structuring Go code

How to Determine if a Year Is a Leap Year

Language

  • unknown

by

Not all years are of the same length — some are shorter or longer than others — so if you’re writing code that deals with dates and durations, it may be important to take account of this.

What Is a Leap Year?

In the Gregorian calendar, a leap year has 366 days, whereas other years have only 365.

The extra day is known as a leap day. (The technical term is an intercalation day, but that’s harder to remember!)

A woman leaping over a rocky chasm, used as a visual metaphor for a leap year.
Leap years are interesting, but they’re not as impressive as this mighty leap.

Every year that is not a leap year is known as a common year. They are called this simply because they happen more often than leap years.

During a leap year, the month of February has 29 days, whereas the month is only usually made up of 28 days.

Approximately one out of every four years is a leap year.

Why Do We Need Leap Years?

A year is defined as the time taken by a planet to revolve around its star.

Since different planets in our Solar System revolve around the Sun at different speeds and following different paths, a year on one planet can be longer or shorter than on another.

For example, on the planet Venus, there are around 225 Earth days in a year. On Mars, there are 687 Earth days in a year.

An image of the planet Venus, which is the second planet from the Sun and is Earth's closest planetary neighbour
An image of the planet Venus taken by NASA’s Mariner 10 spacecraft in 1974.

However, even down here on Earth, there isn’t exactly a whole number of days in a year.

This is because the Earth takes, on average, 365.2422 days to revolve around the Sun.

Our years can only contain either 365 or 366 days, since we’re not used to working with fractional days, but this means that our calendar can get out of sync with the rotation of the Earth, on which it is ultimately based.

So when we add a leap day, we are trying to make our system better reflect the timing of this astronomical event.

This is why we have leap years at regular intervals.

What Is the Gregorian Calendar?

The Gregorian calendar is the system that is now used in most of the world for dating events.

It gets its name because it was first introduced in Europe by Pope Gregory XIII in 1582.

Painting of Pope Gregory XIII being met by Japanese ambassadors in 1585. Pope Gregory gave his name to the Gregorian calendar.
This painting shows Japanese ambassadors meeting with Pope Gregory XIII in 1585.

Before that, the Julian calendar had been used since 45 BC (which was named after Julius Caesar, the ancient Roman general who proposed that it should be adopted in the territories ruled by Rome).

The Gregorian calendar is more accurate than the Julian calendar, because of the way that it handles leap years, as we will see when we look at some Go code.

Attempting to Determine Whether a Year Is a Leap Year in Go

Since it appears that every fourth year is a leap year, it follows that we should be able to work out whether a particular year is a leap year by checking if the number that represents our year is divisible by four.

In other words, if we’re working with the year 2000, can we divide 4 into 2000 without having a remainder?

Let’s write some Go code to find out:

package main

import "fmt"

func isLeapYear_naiveAttempt(year int) bool {
	return year%4 == 0
}

func main() {
	fmt.Println(isLeapYear_naiveAttempt(2000)) // true
	fmt.Println(isLeapYear_naiveAttempt(1980)) // true
	fmt.Println(isLeapYear_naiveAttempt(1981)) // false
	fmt.Println(isLeapYear_naiveAttempt(2023)) // false
	fmt.Println(isLeapYear_naiveAttempt(1892)) // true
	fmt.Println(isLeapYear_naiveAttempt(1800)) // false
}

You can see above that 2000 is divisible by 4, which means that the year when we entered the second millennium was, in fact, a leap year.

We have also printed out the results for some other years, so we can test our helpful little function.

However, you should also be able to see from the name that I gave to the function — calling it a “naive attempt” — that I don’t think it’s a perfect solution.

I don’t know if you noticed or not, but one of the results printed out above is incorrect, and, in the next section, we’re going to improve our function to fix that problem.

Completing Our Algorithm

It’s almost true that every fourth year is a leap year — but it’s not quite true.

There’s just one more rule that we have to take account of, if we want our function to give accurate results.

If a year is both divisible by 100 and not divisible by 400, then it can never be a leap year.

(In fact, this rule was introduced in the transition from the Julian to the Gregorian calendar, mentioned above, and it’s why the latter system is more accurate, since it has ever-so-slightly fewer leap years over long spans of time.)

So let’s add a few extra conditions to our function in order to make use of that newfound knowledge:

package main

import "fmt"

func isLeapYear(year int) bool {
	if year%400 == 0 {
		return true
	} else if year%100 == 0 {
		return false
	} else if year%4 == 0 {
		return true
	}

	return false
}

func main() {
	fmt.Println(isLeapYear(2000)) // true
	fmt.Println(isLeapYear(1980)) // true
	fmt.Println(isLeapYear(1981)) // false
	fmt.Println(isLeapYear(2023)) // false
	fmt.Println(isLeapYear(1892)) // true
	fmt.Println(isLeapYear(1800)) // false
}

You can see that the result for the year 1800 is now false — whereas when we used the previous version of our function, we were given the incorrect answer of true.

The isLeapYear function is now accurate and can be used in real-world applications.

Condensing the Function’s Logic onto a Single Line

It is also possible to write a one-liner that performs the same job, as shown below:

package main

func isLeapYear_oneLiner(year int) bool {
	return year%4 == 0 && (year%100 != 0 || year%400 == 0)
}

However, I prefer the slightly longer version of the function — not just for aesthetic reasons, but also because the greater use of braces and whitespace helps me to reason through the logic.

Using My Go Package

In order to make things easier, and to encourage code reusability, I have created a simple Go package that can be imported into your own code whenever you want to work out whether a particular year is a leap year or not.

You can view its GitHub repository online, where there is documentation provided.

Below is a final example that uses my package to populate a list of all the years between 1900 and 1999 inclusive that were leap years:

package main

import (
	"fmt"
	leapYear "github.com/theTardigrade/golang-leapYear"
)

func main() {
	const startYear = 1900
	const endYear = 1999

	leapYearsBetweenStartAndEndYears := make([]int, 0, (endYear-startYear)+1)

	for year := startYear; year <= endYear; year++ {
		if leapYear.Is(year) {
			leapYearsBetweenStartAndEndYears = append(
				leapYearsBetweenStartAndEndYears,
				year,
			)
		}
	}

	fmt.Printf(
		"There were %d years between %d and %d that were leap years.\n",
		len(leapYearsBetweenStartAndEndYears),
		startYear,
		endYear,
	)

	fmt.Println("They are listed below:")

	for _, year := range leapYearsBetweenStartAndEndYears {
		fmt.Printf("\t%d\n", year)
	}
}

We initialized the leapYearsBetweenStartAndEndYears slice with a capacity of 100 (which is the result of calculating (endYear-startYear)+1).

Since this is exactly the number of years that we iterate through, we can be confident that the slice will not need to hold more values than that — although it does, of course, hold fewer, since not all of our years will turn out to be leap years.

Running the code should produce the following output:

There were 24 years between 1900 and 1999 that were leap years.
They are listed below:
	1904
	1908
	1912
	1916
	1920
	1924
	1928
	1932
	1936
	1940
	1944
	1948
	1952
	1956
	1960
	1964
	1968
	1972
	1976
	1980
	1984
	1988
	1992
	1996

We can see that none of the leap years listed above are divisible by 100, since the year 1900 is excluded by the rule that we talked about above, but all of the years are divisible by 4, since this is the case for all leap years.

Leave a Reply

Your email address will not be published. Required fields are marked *