clockworkpc
clockworkpc

Reputation: 688

Determine if a given year is a leap year

UPDATE The problem I was trying to solve required a bit more than the Date implementation, as it required a method to take into account both the post-1752 Gregorian and the pre-1752 Julian calendars' differing definitions of a leap year.

The following is my solution, which passed all my RSpecs and is a considerable improvement on the nested conditional I started with:

def leap_year?(year)

  gregorian_year = true if year >= 1752
  julian_year = true if year < 1752

  gregorian_test = true if year % 4 == 0 && year % 100 != 0 || year % 400 == 0
  julian_test = true if year % 4 == 0

  case
  when gregorian_year && gregorian_test
    true
  when julian_year && julian_test
    true
  else
    false
  end
end

ORIGINAL QUESTION

I wrote a simple, ugly nested conditional to achieve the following:

To achieve the following:

Return "true" if the year meets the following conditions:

  1. Is divisible by four
  2. AND is not a century year (e.g.) 1900
  3. UNLESS it is divisible by 400 (e.g. 400, 800, 2000)

I wrote an ugly nested conditional:

if year % 4 == 0
  puts "Divisible by four"
  if year % 100 == 0
    puts "Century year"
    if year % 400 == 0
      puts "Quad-century year, leap year"
      true
    else
      "Not a Quad-century year, not a leap year"
      false
    end
  else
    puts "Not a century year, leap year"
    true
  end
else
  puts "Not divisible by four: not a leap year."
  false
end

I tried to achieve the same with a case conditional, but it fails to detect the number 2016 as leap year:

case year
when (year % 4 == 0 && year % 100 != 0)
  true
when year % 400
  true
when year % 4 != 0
  false
end

Two questions:

  1. What am I doing wrong in my case conditional?
  2. Is there a better way to achieve this?

Upvotes: 1

Views: 882

Answers (2)

Mate Solymosi
Mate Solymosi

Reputation: 5977

If you would like to determine if a given year is a leap year, that has already been implemented for you in the Date class as Date#leap?:

Date.leap?(2000)
#=> true

# Or, alternatively:

Date.gregorian_leap?(1900)
#=> false

Date.julian_leap?(1900)
#=> true

More info in the Ruby documentation: Date#leap?

If you would like to build it yourself regardless, this should work:

def leap_year?(year)
  return false unless year % 4 == 0
  return true unless year % 100 == 0
  year % 400 == 0
end

leap_year?(2016)
#=> true

Upvotes: 3

Eric Duminil
Eric Duminil

Reputation: 54223

Remove the year from case year if your when arguments are all boolean:

  case
  when (year % 4 == 0 && year % 100 != 0)
    true
  when year % 400 == 0
    true
  else
    false
  end

You can check it works :

def is_leap?(year)
  case
  when (year % 4 == 0 && year % 100 != 0)
    true
  when year % 400 == 0
    true
  else
    false
  end
end

require 'date'

p (0..2050).all?{|y| Date.leap?(y) == is_leap?(y)}
# true

This variant might be a bit more readable :

def is_leap?(year)
  case
  when year % 400 == 0
    true
  when year % 100 == 0
    false
  when year % 4 == 0
    true
  else
    false
  end
end

Finally, you could just write a single expression without any if or case :

year % 400 == 0 || (year % 4 == 0 && year % 100 != 0)

Upvotes: 3

Related Questions