14

Is there a better way to calculate number of leap years between two years. Assuming I have start date and end date.

I have my code, but I think there should be more elegant way.

calling code:

var numberOfLeapYears = NumberOfLeapYears(startDate.Year + 1, endDate.Year - 1);

function itself:

    private static int NumberOfLeapYears(int startYear, int endYear)
    {
        var counter = 0;

        for (var year = startYear; year <= endYear; year++)
            counter += DateTime.IsLeapYear(year) ? 1 : 0;

        return counter;
    }

So if I have startDate = "10/16/2006" and endDate = "4/18/2004" I should only have 1 leap year (2000) in result. Another words startDate's Year and endDate's year should not be calculated, only years in between.

Thanks in advance for your help.

Liam
  • 27,717
  • 28
  • 128
  • 190
Vlad Bezden
  • 83,883
  • 25
  • 248
  • 179
  • 3
    This seems like a reasonable implementation – Xavier Poinas Jan 03 '11 at 19:19
  • I assume you meant 10/16/1996. In this case, you don't care about the 2/29/2004, which was a leap day that occurred prior to the end date of 4/18/2004? – Anthony Pegram Jan 03 '11 at 19:19
  • 3
    Do you need to take into account that the leap year calculations were different before the Gregorian calendar reform? Also, do you need to take into account that some countries adopted the reformed calendar at different times? The number of leap years between two dates *in England* can be different than the number of leap years between two dates *in the US* for example. – Eric Lippert Jan 03 '11 at 23:18
  • @Eric Lippert. No I don't need to take into account leap years different before Gregorian calendar reform. I don't need to take into account any countris (US only). Thanks Eric. – Vlad Bezden Jan 04 '11 at 12:57
  • If you think of the number of loops, you can accelerate the process starting jumping by 4 years once you find the first one. – Romias Dec 10 '11 at 18:23

7 Answers7

34

You can count it using analytic approach. A year is a leap year if it can be divided by 4, but can't be divided by 100, except of case when it can be divided by 400. Assuming that you can count such number by following code:

static int LeapYearsBetween(int start, int end)
{
    System.Diagnostics.Debug.Assert(start < end);
    return LeapYearsBefore(end) - LeapYearsBefore(start + 1);
}

static int LeapYearsBefore(int year)
{
    System.Diagnostics.Debug.Assert(year > 0);
    year--;
    return (year / 4) - (year / 100) + (year / 400);
}

Some kind of math magic. It is much effective solution than using LINQ.

Liam
  • 27,717
  • 28
  • 128
  • 190
Victor Haydin
  • 3,518
  • 2
  • 26
  • 41
  • 1
    I like this approach since there is no iteration for each year. In the begging I like more LINQ approach, but after analyzing your code I found it should not have impact on performance since LINQ approach need to go through each year and find if it is leap or not. Yours solution just pure math. I tested and it gives the same result as LINQ. Thanks again. – Vlad Bezden Jan 03 '11 at 19:53
  • @Vlad Bezden, currently all the years are bellow 10000, so there is no feasible performance issue and because it will be done in memory it's not bad in all, in this cases I personally prefer clean code, if it was for database read write, yes it would be better to do it better. – Saeed Amiri Jan 03 '11 at 19:56
  • 2
    Personally, I'd try to stick to O(1) algorithms over O(n) wherever possible. – Jeff Mercado Jan 04 '11 at 10:10
  • 2
    FYI: Last method is the same as "(year-1) * 0.2425" if using doubles. :) – James Wilkins Nov 17 '13 at 06:45
  • 1
    This is pretty fantastic! Thanks so much for sharing. – aLearner Apr 25 '14 at 06:07
11

You can do it with LINQ simply as bellow:

var leepYears = Enumerable.Range(startYear, endYear - startYear + 1)
                              .Count(x => DateTime.IsLeapYear(x));
Saeed Amiri
  • 22,252
  • 5
  • 45
  • 83
  • 3
    @Vlad Bezden LINQ is great tool, but for this task there is much efficient solution (see my answer). When range is large LINQ solution is slow, but my algorithm is independent from range size. – Victor Haydin Jan 03 '11 at 19:47
  • +1 I really this code - it's easy to read and the intent of code is very clear. – Daniel James Bryars Jan 03 '11 at 19:57
  • @Saeed yes I like how code is clean, but agree with mace that LINQ solution is slower especially when we have big difference between startYear and endYear. – Vlad Bezden Jan 03 '11 at 21:20
  • 1
    @Vlad Bezden, but in most cases you have no big difference in start year and end year biggest possible is 2000 which is not big in this days. – Saeed Amiri Jan 04 '11 at 05:32
3

This should perform much better for large spans of time:

public int LeapYearsBetween(int year1, int year2)
{
    var y1 = new DateTime(year1, 1, 1);
    var y2 = new DateTime(year2, 1, 1);
    var nonLeapDays = 365 * (y2.Year - y1.Year);
    var leapDays = (y2 - y1).Days - nonLeapDays;
    return leapDays;
}

Note that this counts the earlier year if it is a leap year, but not the later year. You'll need to modify the function if you need different behavior.

phoog
  • 42,068
  • 6
  • 79
  • 117
2

Just another code with primitives types

    public static int CountLeapYears(int startYear, int endYear) 
    {
        int acc = 0;

        while (true)
        {
            if ((startYear % 4 == 0 && startYear % 100 != 0) || startYear % 400 == 0)
                acc++;
            if (startYear + 1 == endYear) break;
            startYear++;
        }

        return acc;
    }
2

Another Linq :-)

int start = 1980;
int end = 2000;
var count = Enumerable.Range(start, end - start + 1)
                      .Aggregate(0, (a, b) => DateTime.IsLeapYear(b) ? a + 1 : a);
Jahan Zinedine
  • 14,616
  • 5
  • 46
  • 70
  • Actually I like mace's solution, since it is pure calculation and no iteration, so I think mace's solution has less CPU utilization. I have more than 100K records to calculate Leap Years. – Vlad Bezden Jan 03 '11 at 19:56
0

On your maths (divisible by 100 are not leap years unless they are also evenly divisible by 400) this would mean 1900 was not a leap year? wrong

This means the following is correct: Couldn't you just test the first 4 years and find the first leap year and subtract that from the total years and mod 4? basically: end_year - first_leap_year_found mod 4 = 0 is a leap year

lwnuclear
  • 49
  • 4
  • 1
    If you call `DateTime.IsLeapYear(1900)` this will return `false`. And if you take a look at [Wikipedia](http://en.wikipedia.org/wiki/Leap_year) the year 1900 was not a leap year. So Victors calculation is correct and your assumption is false. – Oliver Feb 26 '13 at 12:33
-1

Couldn't you just test the first 4 years and find the first leap year and subtract that from the total years and mod 4?

basically:

end_year - first_leap_year_found mod 4.

I wouldn't be surprised if there are a few tweaks in there to account for the fact that the entire date is specified, not just the year, but those alterations should be simple as well.

You end up only having to use the DateTime.IsLeapYear(year) statement at most 4 times, and then it's simple math after that.

jaydel
  • 14,389
  • 14
  • 62
  • 98
  • 2
    This ignores the fact that years that are evenly divisible by 100 are not leap years unless they are also evenly divisible by 400. – chezy525 Jan 03 '11 at 19:50
  • ah, yes, agreed. Of course, that's another simple tweak. But the more 'simple tweaks' I claim, the less simple my proposed solution, so good point :) – jaydel Jan 04 '11 at 13:08