Feature #21972 ยป birthday_and_age.patch
| 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
|
||