Project

General

Profile

Feature #21972 ยป birthday_and_age.patch

jinroq (Jinroq SAITOH), 03/31/2026 02:38 PM

View differences:

lib/date.rb
class Date
VERSION = "3.5.1" # :nodoc:
RUBY_BIRTHDAYS = { # :nodoc:
nil => [1993, 2, 24].freeze, # The name "Ruby" was decided
0.95 => [1995, 12, 21].freeze, # First public release
1.0 => [1996, 12, 25].freeze, # First stable release
}.freeze
# call-seq:
# Date.birthday -> date
# Date.birthday(nil) -> date
# Date.birthday(0.95) -> date
# Date.birthday(1.0) -> date
#
# Returns the birthday of the Ruby programming language.
#
# With no argument (or +nil+), returns the day the name "Ruby" was decided
# (1993-02-24). With +0.95+, returns the first public release date
# (1995-12-21). With +1.0+, returns the first stable release date
# (1996-12-25).
#
# Date.birthday.to_s # => "1993-02-24"
# Date.birthday(0.95).to_s # => "1995-12-21"
# Date.birthday(1.0).to_s # => "1996-12-25"
#
# Raises ArgumentError for any other argument.
def self.birthday(version = nil)
ymd = RUBY_BIRTHDAYS[version]
raise ArgumentError, "unsupported version: #{version.inspect}" unless ymd
new(*ymd)
end
# call-seq:
# Date.age -> integer
# Date.age(nil) -> integer
# Date.age(0.95) -> integer
# Date.age(1.0) -> integer
#
# Returns the age of the Ruby programming language in years.
#
# Calculates the number of full years elapsed since the birthday
# returned by Date.birthday with the same argument.
#
# Date.age # => 33 (as of 2026-03-24)
# Date.age(0.95) # => 30
# Date.age(1.0) # => 29
#
# Raises ArgumentError for any unsupported or invalid argument.
def self.age(version = nil)
bd = birthday(version)
today = self.today
age = today.year - bd.year
age -= 1 if today.month < bd.month ||
(today.month == bd.month && today.day < bd.day)
age
end
# call-seq:
# infinite? -> false
#
test/date/test_date_birthday.rb
# frozen_string_literal: true
require "test/unit"
require "date"
class TestDateBirthday < Test::Unit::TestCase
# --- Normal cases ---
def test_birthday_no_argument
d = Date.birthday
assert_equal(Date.new(1993, 2, 24), d)
assert_equal("1993-02-24", d.to_s)
end
def test_birthday_nil
d = Date.birthday(nil)
assert_equal(Date.new(1993, 2, 24), d)
end
def test_birthday_0_95
d = Date.birthday(0.95)
assert_equal(Date.new(1995, 12, 21), d)
assert_equal("1995-12-21", d.to_s)
end
def test_birthday_1_0
d = Date.birthday(1.0)
assert_equal(Date.new(1996, 12, 25), d)
assert_equal("1996-12-25", d.to_s)
end
# --- Return type ---
def test_birthday_returns_date_instance
assert_instance_of(Date, Date.birthday)
assert_instance_of(Date, Date.birthday(0.95))
assert_instance_of(Date, Date.birthday(1.0))
end
# --- ArgumentError for unsupported versions ---
def test_birthday_unsupported_version
assert_raise(ArgumentError) { Date.birthday(0.0) }
assert_raise(ArgumentError) { Date.birthday(1.8) }
assert_raise(ArgumentError) { Date.birthday(1.9) }
assert_raise(ArgumentError) { Date.birthday(2.0) }
assert_raise(ArgumentError) { Date.birthday(3.0) }
assert_raise(ArgumentError) { Date.birthday(99.0) }
end
# --- Boundary: values close to valid versions ---
def test_birthday_boundary_close_to_0_95
assert_raise(ArgumentError) { Date.birthday(0.94) }
assert_raise(ArgumentError) { Date.birthday(0.96) }
end
def test_birthday_boundary_close_to_1_0
assert_raise(ArgumentError) { Date.birthday(0.99) }
assert_raise(ArgumentError) { Date.birthday(1.01) }
end
# --- ArgumentError for invalid types ---
def test_birthday_invalid_type_string
assert_raise(ArgumentError) { Date.birthday("1.0") }
assert_raise(ArgumentError) { Date.birthday("0.95") }
assert_raise(ArgumentError) { Date.birthday("ruby") }
end
def test_birthday_invalid_type_symbol
assert_raise(ArgumentError) { Date.birthday(:ruby) }
end
def test_birthday_invalid_type_array
assert_raise(ArgumentError) { Date.birthday([1.0]) }
end
def test_birthday_invalid_type_hash
assert_raise(ArgumentError) { Date.birthday({}) }
end
def test_birthday_invalid_type_boolean
assert_raise(ArgumentError) { Date.birthday(true) }
assert_raise(ArgumentError) { Date.birthday(false) }
end
# --- Edge: negative and special floats ---
def test_birthday_negative_version
assert_raise(ArgumentError) { Date.birthday(-1.0) }
end
def test_birthday_infinity
assert_raise(ArgumentError) { Date.birthday(Float::INFINITY) }
assert_raise(ArgumentError) { Date.birthday(-Float::INFINITY) }
end
def test_birthday_nan
assert_raise(ArgumentError) { Date.birthday(Float::NAN) }
end
# ==========================================================================
# Date.age
# ==========================================================================
# --- Normal cases ---
def test_age_no_argument
today = Date.today
expected = calc_expected_age(Date.new(1993, 2, 24), today)
assert_equal(expected, Date.age)
end
def test_age_nil
today = Date.today
expected = calc_expected_age(Date.new(1993, 2, 24), today)
assert_equal(expected, Date.age(nil))
end
def test_age_0_95
today = Date.today
expected = calc_expected_age(Date.new(1995, 12, 21), today)
assert_equal(expected, Date.age(0.95))
end
def test_age_1_0
today = Date.today
expected = calc_expected_age(Date.new(1996, 12, 25), today)
assert_equal(expected, Date.age(1.0))
end
# --- Return type ---
def test_age_returns_integer
assert_instance_of(Integer, Date.age)
assert_instance_of(Integer, Date.age(0.95))
assert_instance_of(Integer, Date.age(1.0))
end
# --- Age is non-negative ---
def test_age_is_non_negative
assert_operator(Date.age, :>=, 0)
assert_operator(Date.age(0.95), :>=, 0)
assert_operator(Date.age(1.0), :>=, 0)
end
# --- Consistency: age without version >= age with version ---
def test_age_ordering
assert_operator(Date.age, :>=, Date.age(0.95))
assert_operator(Date.age(0.95), :>=, Date.age(1.0))
end
# --- Birthday boundary: exact birthday date ---
def test_age_on_exact_birthday
bd = Date.new(1993, 2, 24)
this_year_bd = Date.new(Date.today.year, bd.month, bd.day)
expected = this_year_bd.year - bd.year
assert_equal(expected, calc_expected_age(bd, this_year_bd))
end
def test_age_day_before_birthday
bd = Date.new(1993, 2, 24)
day_before = Date.new(Date.today.year, bd.month, bd.day) - 1
expected = day_before.year - bd.year - 1
assert_equal(expected, calc_expected_age(bd, day_before))
end
# --- ArgumentError (delegates to birthday) ---
def test_age_unsupported_version
assert_raise(ArgumentError) { Date.age(0.0) }
assert_raise(ArgumentError) { Date.age(1.8) }
assert_raise(ArgumentError) { Date.age(2.0) }
assert_raise(ArgumentError) { Date.age(99.0) }
end
def test_age_invalid_type
assert_raise(ArgumentError) { Date.age("1.0") }
assert_raise(ArgumentError) { Date.age(:ruby) }
assert_raise(ArgumentError) { Date.age([1.0]) }
assert_raise(ArgumentError) { Date.age(true) }
end
def test_age_special_floats
assert_raise(ArgumentError) { Date.age(Float::INFINITY) }
assert_raise(ArgumentError) { Date.age(-Float::INFINITY) }
assert_raise(ArgumentError) { Date.age(Float::NAN) }
end
private
def calc_expected_age(birthday, today)
age = today.year - birthday.year
age -= 1 if today.month < birthday.month ||
(today.month == birthday.month && today.day < birthday.day)
age
end
end
    (1-1/1)