Since Tek-Tips has been most helpful to me over the years, I've put this together to help new Programmers (and maybe some old ones) with a matter that seems to crop up regularly. This Tutorial will provide a little history about the Gregorian Calendar explaining exactly when Leap Years occur, and provide several ways to evaluate whether or not a given Year is a Leap Year using Visual Basic. If you wish to obtain further information, the small "superscript" numbers refer to Wikipedia references at the bottom of the page.

HISTORY

Many people make the assumption that a Leap Year^{1} falls every 4th year, and with every 4th year in the last hundred or so being Leap Years, the assumption certainly rings true. With the Gregorian Calendar^{2} however, this assumption is false.

The Gregorian Calendar was introduced on the 24th of February 1582 by Pope Gregory VIII^{3}, and is the Calendar that most of the world still uses today. It was introduced to restore the inaccuracy of the Julian Calendar^{4} (which had been in use since 45BC) which had accumulated a 10 day drift in relation to the Vernal Equinox^{5}, and ultimately, the day Easter^{6} is celebrated.

The Gregorian Calendar addressed the drift by specifying a one-off 10 day "skip" to bring the Calendar back into synchronization with the seasons. This 10 day "skip" occured in October 1582, where the 4th was followed by the 15th.

The Calendar also adopted a new formula for Leap Years in order to keep the Calendar accurate without further modifications. The Leap Year formula was changed from the Julian specification of every 4 years to "every year that is exactly divisible by 4, excluding years that are exactly divisible by 100, but including years that are exactly divisible by 400".

It has since been suggested that a further adjustment to the Leap Year formula (see "The 4000 Proposal" below) would make the Calendar even more accurate, but this proposal has not been officially adopted. So for now, a Gregorian Leap Year must adhere to the following: Be divisible by 4 AND ( NOT Be divisible by 100 OR Be divisible by 400 )

The best way to calculate this (we will see why later) is to reverse the formula, so that the divisors are in "highest to lowest" order. We therefore re-write the formula as: ( Be divisible by 400 ORNOT Be divisible by 100 ) AND Be divisible by 4

An example of how this affects when Leap Years occur, is shown in the list below. Note the Centurian Years 1500, 1600, 1700, 1800, 1900, 2000 and 2100. .... 1496: True => NOT Divisible by 100 1500: False => NOT Divisible by 400 1504: True => NOT Divisible by 100 .... 1596: True => NOT Divisible by 100 1600: True => Divisible by 400 1604: True => NOT Divisible by 100 .... 1696: True => NOT Divisible by 100 1700: False => NOT Divisible by 400 1704: True => NOT Divisible by 100 .... 1796: True => NOT Divisible by 100 1800: False => NOT Divisible by 400 1804: True => NOT Divisible by 100 .... 1896: True => NOT Divisible by 100 1900: False => NOT Divisible by 400 1904: True => NOT Divisible by 100 .... 1996: True => NOT Divisible by 100 2000: True => Divisible by 400 2004: True => NOT Divisible by 100 .... 2096: True => NOT Divisible by 100 2100: False => NOT Divisible by 400 2104: True => NOT Divisible by 100 ....

We can see from the list that the last "skipped" Leap Year was over one hundred years ago in 1900. The next skip won't occur until the year 2100.

The basics to remember are "400, 100, 4" and exit with "True, False, True". (or, 4 = True and 1 = False)

THE MATHEMATICS

Because all the divisors we are testing for are divisible by the divisors below them (ie: 400 is divisible by both 100 and 4; and 100 is divisible by 4), in mathematical terms, we can simply test for each divisor from highest to lowest, and exit with the response that we require at the first occurance of a remainder of zero.

Thus: Divide by 400: If the remainder = 0, the Year is a Leap Year, so exit. Otherwise: Divide by 100: If the remainder = 0, the Year is not a Leap Year, so exit. Otherwise: Divide by 4: If the remainder = 0, the Year is a Leap Year, so exit. Otherwise: The Year is not a Leap Year.

This can be better expressed using modulus: If (Year mod 400) = 0: Exit True If (Year mod 100) = 0: Exit False If (Year mod 4) = 0: Exit True Exit False

Example 1: 1900 mod 400 = 300 Not zero, therefore goto next step... 1900 mod 100 = 0 Is zero, therefore 1900 is not a Leap Year.

Example 2: 1951 mod 400 = 300 Not zero, therefore goto next step... 1951 mod 100 = 51 Not zero, therefore goto next step... 1951 mod 4 = 3 Not zero, therefore 1951 is not a Leap Year.

Example 3: 1996 mod 400 = 396 Not zero, therefore goto next step... 1996 mod 100 = 96 Not zero, therefore goto next step... 1996 mod 4 = 0 Is zero, therefore 1996 is a Leap Year.

Example 4: 2000 mod 400 = 0 Is zero, therefore 2000 is a Leap Year.

TRANSFORMING IT ALL TO CODE

There are a few different ways to transform the formula into Visual Basic code.

Method 1: Using "If - ElseIf - Then"

CODE

Function IsLeapYear1(iYear As Integer) As Boolean If (iYear Mod 400) = 0 Then IsLeapYear1 = True ElseIf (iYear Mod 100) = 0 Then IsLeapYear1 = False ElseIf (iYear Mod 4) = 0 Then IsLeapYear1 = True Else IsLeapYear1 = False End If End Function

Method 2: Using "Select Case"

CODE

Function IsLeapYear2(iYear As Integer) As Boolean Select Case True Case ((iYear Mod 400) = 0): IsLeapYear2 = True Case ((iYear Mod 100) = 0): IsLeapYear2 = False Case ((iYear Mod 4) = 0): IsLeapYear2 = True Case Else: IsLeapYear2 = False End Select End Function

Putting mathematics aside, the easiest (and most overlooked) way to test for Leap Years in code, is to use inbuilt functions and check the date one day prior to the 1st of March. In VB, we use the inbuilt DateAdd and Day functions. The only drawback here, is the limitation of VB's Date variable, which has a maximum value of 31/Dec/9999. For most applications, this won't present any problem, but it is a limitation all the same and should be respected as such when programming.

Method 3: Using "DateAdd"

CODE

Function IsLeapYear3(iYear As Integer) As Boolean Dim dDate As Date dDate = "1/March/" & iYear dDate = DateAdd("d", -1, dDate) If Day(dDate) = 29 Then IsLeapYear3 = True Else IsLeapYear3 = False End If End Function

Now that we understand what to test for and why, we can simplify the whole process into one line of code in two different ways, as shown in Methods 4 and 5. Note that Method 5 still has the same limitations as Method 3 above.

Method 4:

CODE

Function IsLeapYear4(iYear As Integer) As Boolean IsLeapYear4 = (((iYear Mod 400) = 0) Or ((iYear Mod 100) <> 0)) And ((iYear Mod 4) = 0) End Function

Method 5:

CODE

Function IsLeapYear5(iYear As Integer) As Boolean IsLeapYear5 = (Day(DateAdd("d", -1, "1/March/" & iYear)) = 29) End Function

THE "4000" PROPOSAL

As mentioned earlier, it has been suggested that every 4000th year should be made a "common" year, thus making the calendar even more accurate. To date, this has not been adopted, and is unlikely to be adopted in our lifetime. (It would actually be more accurate to make every 8000th year a common year, rather than every 4000th.)

In the unlikely event that this modification is adopted some time soon, Methods 3 and 5 above which use the inbuilt DateAdd and Day functions, will be incorrect for any year divisible by 4000. In fact, many of VB's Date calculations will be inaccurate if they involve any date after 28th February 4000, depending on what you are calculating.

Methods 1, 2 & 4 however, can be easily modified to accept the new formula. Keeping in mind that we test from highest to lowest, and that 4000 (and 8000) are both evenly divisible by all the lower divisors, we can simply insert the new equation at the beginning of our existing routines as follows:

Method 6: Modified Method 1

CODE

Function IsLeapYear6(iYear As Integer) As Boolean If (iYear Mod 4000) = 0 Then IsLeapYear6 = False ElseIf (iYear Mod 400) = 0 Then IsLeapYear6 = True ElseIf (iYear Mod 100) = 0 Then IsLeapYear6 = False ElseIf (iYear Mod 4) = 0 Then IsLeapYear6 = True Else IsLeapYear6 = False End If End Function

Method 7: Modified Method 2

CODE

Function IsLeapYear7(iYear As Integer) As Boolean Select Case True Case ((iYear Mod 4000) = 0): IsLeapYear7 = False Case ((iYear Mod 400) = 0): IsLeapYear7 = True Case ((iYear Mod 100) = 0): IsLeapYear7 = False Case ((iYear Mod 4) = 0): IsLeapYear7 = True Case Else: IsLeapYear7 = False End Select End Function

Method 8: Modified Method 4

CODE

Function IsLeapYear8(iYear As Integer) As Boolean IsLeapYear8 = ((iYear Mod 4000) <> 0) And (((iYear Mod 400) = 0) Or ((iYear Mod 100) <> 0)) And ((iYear Mod 4) = 0) End Function

CONCLUSION

I leave you with this thought: Did you know that the extra day (called the "intercalary day"^{7}) in February is actually the 24th and not the 29th? With this in mind, it's not just people born on the 29th who can brag about having one birthday every four years. Those born on the 24th, 25th, 26th, 27th, and 28th can also claim that their birthday doesn't fall on the correct day except in Leap Years, where an extra day is inserted at 24, pushing their "birthdays" to the correct position.

We really could go for hours on this, couldn't we?