From childhood, we know the concept of the leap year. It has 366 days. February will have an extra day than it usually has. We were not much concerned about this fact until we became Engineers. Developers, to be precise.
Recently the Leap day of 2020 has passed, costing us a few cups of extra coffee. It was simple logic, but a small mistake has the potential to bring chaos to the software world.
In C#, there is a function to add years of a date, which will simply increment the year of the date.
DateTime sampleDate = new DateTime(2019, 1, 1);
DateTime nextSampleDate = date.AddYears(1); //the next date will be 2020 Jan 1
We have an algorithm that generates data for present day of next year. This algorithm is executed daily which ensures the availability of data for the forthcoming year.
But here the leap year became an edge case and put a hole in our algorithm.
Current Day | Data generated for the day |
---|---|
February 27 2019 | February 27 2020 |
February 28 2019 | February 28 2020 |
March 1 2019 | March 1 2020 |
Unfortunately, 2020 is a leap year and the data generation was skipped for the 29th of February. We didn't realize it until the day came. On February 29th of 2020, there was no data in the database. As we handled the case when there is no data, there were no crashes reported.
One way we could have solved the issue was by using
AddDays(365)
instead of AddYears(1)
. After evaluating the outcomes of this implementation, we observed the generation of anti-pattern dates.
Current Day | Data generated day |
---|---|
February 27 2019 | February 27 2020 |
February 28 2019 | February 28 2020 |
March 1 2019 | February 29 2020 |
March 2 2019 | March 1 2020 |
January 1 2020 | December 31 2020 |
There were fewer concerns about the performance since it is executing once in a day. So we decided to generate for both days if there is a possibility of skipping the leap day.
public async Task GenerateData()
{
//Get current utc date
DateTime currentDate = DateTime.UtcNow;
DateTime nextYearDate = currentDate.AddYears(1);
//Generate data for the current date
await GenerateData(nextYearDate);
//check if there is any upcoming leap day
if (nextYearDate.Day == 28 && nextYearDate.Month == 2
&& DateTime.IsLeapYear(nextYearDate.Year))
{
await GenerateData(nextYearDate.AddDays(1));
}
}
public async Task GenerateData(DateTime date)
{
/*
code for generating data for the given date
*/
}
The output of this updated algorithm shows that the data of the leap day gets generated at the right time.
RecurringJob.AddOrUpdate<IGenerationService>("IGenerationService-GenerateData",
gs => gs.GenerateData(), Cron.Daily); //Generate data for the day
Current Day | Data generated days |
---|---|
February 27 2019 | February 27 2020 |
February 28 2019 | February 28 2020, February 29 2020 |
March 1 2019 | March 1 2020 |
March 2 2019 | March 2 2020 |
There exists a significant amount of leap year issues reported around the world. Even though the problem we encountered may seem small, the impact it can create was almost huge. We were able to identify the issue at an early stage and was able to release a Hotfix. Mistakes can happen to anyone. We grow when we learn from our mistakes, and sharing it will help others grow along.