Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What are some examples of leap year bugs?

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:

  • Leap years have a February 29th, while common years do not.
  • Leap years have 366 days in total, while common years have only 365.

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:

  • Category 1: Those that lead to error conditions, such as exceptions, error return codes, uninitialized variables, or endless loops
  • Category 2: Those that lead to incorrect data, such as off-by-one problems in range queries or aggregation

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.

like image 857
Matt Johnson-Pint Avatar asked Aug 16 '19 20:08

Matt Johnson-Pint


People also ask

What are examples of leap years?

Any year that is evenly divisible by 4 is a leap year: for example, 1988, 1992, and 1996 are leap years.

Will 2022 be a leap year?

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.

Is leap year lucky or unlucky?

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.


2 Answers

.NET / C# - Construction from date parts

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);

Common Variation - Birthdays (and other anniversaries)

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);
like image 136
4 revs Avatar answered Sep 23 '22 03:09

4 revs


Win32 / C++ - SYSTEMTIME struct manipulation

Impact 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.

like image 31
Matt Johnson-Pint Avatar answered Sep 23 '22 03:09

Matt Johnson-Pint