How to Determine if a Year Is a Leap Year
Language
- unknown
by James Smith (Golang Project Structure Admin)
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.
Table of Contents
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!)
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.
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.
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.