DateTime sampleDate = new DateTime(2019, 1, 1);
DateTime nextSampleDate = date.AddYears(1); //the next date will be 2020 Jan 1
DateTime sampleDate = new DateTime(2019, 1, 1);
DateTime nextSampleDate = date.AddYears(1); //the next date will be 2020 Jan 1
DateTime sampleDate = new DateTime(2019, 1, 1);
DateTime nextSampleDate = date.AddYears(1); //the next date will be 2020 Jan 1
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.
publicasync Task GenerateData()
{
//Get current utc date
DateTime currentDate = DateTime.UtcNow;
DateTime nextYearDate = currentDate.AddYears(1);
//Generate data for the current dateawaitGenerateData(nextYearDate);
//check if there is any upcoming leap dayif (nextYearDate.Day == 28 && nextYearDate.Month == 2
&& DateTime.IsLeapYear(nextYearDate.Year))
{
awaitGenerateData(nextYearDate.AddDays(1));
}
}
publicasync Task GenerateData(DateTime date)
{
/* code for generating data for the given date */
publicasync Task GenerateData()
{
//Get current utc date
DateTime currentDate = DateTime.UtcNow;
DateTime nextYearDate = currentDate.AddYears(1);
//Generate data for the current dateawaitGenerateData(nextYearDate);
//check if there is any upcoming leap dayif (nextYearDate.Day == 28 && nextYearDate.Month == 2
&& DateTime.IsLeapYear(nextYearDate.Year))
{
awaitGenerateData(nextYearDate.AddDays(1));
}
}
publicasync Task GenerateData(DateTime date)
{
/* code for generating data for the given date */
publicasync Task GenerateData()
{
//Get current utc date
DateTime currentDate = DateTime.UtcNow;
DateTime nextYearDate = currentDate.AddYears(1);
//Generate data for the current dateawaitGenerateData(nextYearDate);
//check if there is any upcoming leap dayif (nextYearDate.Day == 28 && nextYearDate.Month == 2
&& DateTime.IsLeapYear(nextYearDate.Year))
{
awaitGenerateData(nextYearDate.AddDays(1));
}
}
publicasync Task GenerateData(DateTime date)
{
/* code for generating data for the given date */
publicasync Task GenerateData()
{
//Get current utc date
DateTime currentDate = DateTime.UtcNow;
DateTime nextYearDate = currentDate.AddYears(1);
//Generate data for the current dateawaitGenerateData(nextYearDate);
//check if there is any upcoming leap dayif (nextYearDate.Day == 28 && nextYearDate.Month == 2
&& DateTime.IsLeapYear(nextYearDate.Year))
{
awaitGenerateData(nextYearDate.AddDays(1));
}
}
publicasync 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
RecurringJob.AddOrUpdate<IGenerationService>("IGenerationService-GenerateData",
gs => gs.GenerateData(), Cron.Daily); //Generate data for the day
RecurringJob.AddOrUpdate<IGenerationService>("IGenerationService-GenerateData",
gs => gs.GenerateData(), Cron.Daily); //Generate data for the day
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.