A leap year bug is a code defect that creates a problematic, unintended outcome when executed within the context of a leap year, typically within the proleptic Gregorian calendar system.
The last leap year was 2016. The next leap years are 2020 and 2024.
There are two attributes that are unique to leap years:
This post is intended to help others understand the nature of leap year bugs, what they look like in various languages, and how to correct them.
Leap year bugs typically fall into two impact categories:
With each answer, please indicate the programming language and/or platform, as well as the impact category as defined above. (Follow the template used by existing answers please.)
Please create one separate answer per language and type of defect, and vote for your favorite, especially those you have personally encountered (leaving comments with anecdotes where possible).
I will seed a few answers to get started, and update with additional examples over time.
Any year that is evenly divisible by 4 is a leap year: for example, 1988, 1992, and 1996 are leap years.
Why 2022 isn't a leap year. The last leap year was 2020. So 2024 will be our next leap year, a 366-day-long year, with an extra day added to our calendar (February 29). We'll call that extra day a leap day.
The time it takes for the earth to rotate is 365 ¼ days but the calendar year is 365 days, hence once every four years to balance this, we have a leap year and an extra day, February 29th. Because such years are rarer than normal years, they have become lucky omens.
Impact Category 1
Defective Code
DateTime dt = DateTime.Now;
DateTime result = new DateTime(dt.Year + 1, dt.Month, dt.Day);
This code will work properly until dt
becomes February 29th. Then, it will attempt to create a February 29th of a common year, which does not exist. The DateTime
constructor will throw an ArgumentOutOfRangeException
.
Variations include any form of DateTime
or DateTimeOffset
constructor that accepts year, month, and day parameters, when those values are derived from different sources or manipulated without regard to validity as a whole.
Corrected Code
DateTime dt = DateTime.Now;
DateTime result = dt.AddYears(1);
One variation is when determining a user's current birthday without considering leaplings (persons born on February 29th). It also applies to other types of anniversaries, such as hire date, date of service, billing date, etc.
Defective Code
DateTime birthdayThisYear = new DateTime(DateTime.Now.Year, dob.Month, dob.Day);
This approach needs adjustment, such as the following which uses February 28th for common years. (Though, march 1st might be preferred depending on the use case.)
Corrected Code
int year = DateTime.Now.Year;
int month = dob.Month;
int day = dob.Day;
if (month == 2 && day == 29 && !DateTime.IsLeapYear(year))
day--;
DateTime birthdayThisYear = new DateTime(year, month, day);
Corrected Code (alternative implementation)
DateTime birthdayThisYear = dob.AddYears(DateTime.Now.Year - dob.Year);
SYSTEMTIME
struct manipulationImpact Category 1
Defective Code
SYSTEMTIME st;
FILETIME ft;
GetSystemTime(&st);
st.wYear++;
SystemTimeToFileTime(&st, &ft);
This code will work properly until st
becomes February 29th. Then, it will attempt to create a February 29th of a common year, which does not exist. Passing this to any function that accepts a SYSTEMTIME
struct will likely fail.
For example, the SystemTimeToFileTime
call shown here will return an error code. Since that return value is unchecked (which is extremely common), this will result in ft
being left uninitialized.
Corrected Code
SYSTEMTIME st;
FILETIME ft;
GetSystemTime(&st);
st.wYear++;
bool isLeapYear = st.wYear % 4 == 0 && (st.wYear % 100 != 0 || st.wYear % 400 == 0);
st.wDay = st.wMonth == 2 && st.wDay == 29 && !isLeapYear ? 28 : st.wDay;
bool ok = SystemTimeToFileTime(&st, &ft);
if (!ok)
{
// handle error
}
This fix checks for Feb 29th of a common year, and corrects it to Feb 28th.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With