# "Which Century" programming exercise

I recently wrote this up and am wondering if there is a better way to achieve the results I want.

Essentially, we're taking a given year and adding 'th, nd, rd, st' depending on the digit.

Any suggestions on how to shorten this code and make it better?

Keep in mind that this is taking centuries that change every 01st year. For example, year 2000 is the 20th century, but 2001 is the 21st century.

def which_century(string)
year = string.to_i
modifier = 1

return "1st century" if year < 101

if year > 100 && string.end_with?("00")
modifier -= 1
end

century = year / 100 + modifier
p century
if century > 10 && century < 20
return century.to_s << "th century."
end

second_digit = century % 10

case second_digit
when 1 then century.to_s << "st century"
when 2 then century.to_s << "nd century"
when 3 then century.to_s << "rd century"
else
century.to_s << "th century"
end
end

puts "Type in a year:"


Let's clarify the logic. The code simplification will follow. The rules are:

1. "th" years are those ending in 4, 5, 6, 7, 8, 9, or 0. Additionally, the 11th, 12th, and 13th centuries are exceptions also ending in "th".
2. Years ending in 1 are "st" years.
3. Years ending in 2 are "nd" years.
4. Years ending in 3 are "rd" years.

Now just translate the above into code:

def which_century(string)
century  = (string.to_f / 100).ceil
year     = century % 10
nth_year = (11..13).include?(century) || !(1..3).include?(year)
return "#{century}th century" if nth_year
return "#{century}st century" if year == 1
return "#{century}nd century" if year == 2
return "#{century}rd century" if year == 3
end


I would be more formulaic regarding the century calculation:

century = (year - 1) / 100 + 1


Also, consider using .ordinalize from ActiveSupport (via require 'active_support/core_ext/integer' - you can require more of ActiveSupport but this keeps it minimal)

Then your code gets much shorter:

def which_century(year)
century = (year.to_i - 1)/100 + 1
century.ordinalize + " century"
end


Test cases:

> which_century(2001)
=> "21st century"
> which_century(2000)
=> "20th century"
> which_century(1999)
=> "20th century"
> which_century(100)
=> "1st century"
> which_century(101)
=> "2nd century"
> which_century(99)
=> "1st century"


I wonder if it would be more intuitive if, instead of dividing by 100 to get a century, and using second_digit, you instead used ranges.

For example:

year.between?(1001,2000)


You do still need century for the final result, but you use that logic quite a bit so maybe define a method for that ...

def year_to_century_string(year, ordinal)
(year/100+1).to_s + ordinal + " century"
end


The calculation of the century for a year might be a separate method also.

If you wanted to be a bit more object-ey, you could define a class Year that encapsulates these methods, so you can:

Year.new(2001).century


... and ...

Year.new(2001).century_string


In other words, name the concept that the string/number represents, and add behaviour to it that is specific to that concept.

The method, as it stands, does quite a lot of processing and names a few concepts that it does not expose (e.g. century) -- these can often be clues that the method should be converted to a class with its own behaviour.

You could also define a refinement on FixNum that would allow you to ...

1995.to_year.ordinalised_century