Nhận tuổi của mọi người trong Ruby


125

Tôi muốn có được tuổi của một người từ ngày sinh nhật của nó. now - birthday / 365không hoạt động, bởi vì một số năm có 365 ngày. Tôi đã đưa ra mã sau đây:

now = Date.today
year = now.year - birth_date.year

if (date+year.year) > now
  year = year - 1
end

Có cách nào để tính tuổi của Ruby hơn không?


6
Tôi thích câu hỏi này vì nó làm nổi bật ý tưởng rằng có nhiều cách làm "nhiều Ruby hơn" và "ít Ruby hơn". Điều quan trọng không chỉ là chính xác về mặt logic (mà bạn có thể bằng cách sao chép câu trả lời C #), mà còn đúng về mặt phong cách. Và câu trả lời của Adinochestva sử dụng tốt thành ngữ Ruby.
James A. Rosen

Câu trả lời:


410

Tôi biết tôi đến bữa tiệc muộn ở đây, nhưng câu trả lời được chấp nhận sẽ phá vỡ khủng khiếp khi cố gắng tìm ra tuổi của một người sinh vào ngày 29 tháng 2 vào một năm nhuận. Điều này là do cuộc gọi để birthday.to_date.change(:year => now.year)tạo một ngày không hợp lệ.

Tôi đã sử dụng mã sau đây để thay thế:

require 'date'

def age(dob)
  now = Time.now.utc.to_date
  now.year - dob.year - ((now.month > dob.month || (now.month == dob.month && now.day >= dob.day)) ? 0 : 1)
end

4
Sử dụng cái này, không phải cái được đánh dấu kiểm không thể xử lý năm nhuận
bgcode

Tại sao bạn trở về 0 || 1 thay vì true| | false?
0112

1
@ alex0112 Bởi vì kết quả (0 hoặc 1) của điều kiện thừa nhận gây nhầm lẫn đó được trừ vào sự khác biệt trong năm giữa bây giờ và ngày sinh. Nó được dự định để tìm hiểu xem người đó đã có sinh nhật của họ trong năm nay chưa và nếu không, họ ít hơn 1 tuổi so với sự khác biệt giữa các năm.
philnash

2
@andrej now = Date.today hoạt động nhưng lưu ý rằng nó không xử lý các sự cố với Timezone. Trong Rails Date.today trả về một ngày dựa trên múi giờ của hệ thống. ActiveRecord trả về thời gian dựa trên múi giờ được định cấu hình Ứng dụng của bạn. Nếu múi giờ hệ thống khác với múi giờ ứng dụng của bạn, bạn thực sự sẽ so sánh thời gian từ hai múi giờ khác nhau, điều này sẽ không chính xác lắm.
Onwuemene yêu thích 17/03/2016

Đây thực sự là giải pháp đơn giản nhất?
Marco Prins

50

Tôi đã tìm thấy giải pháp này để hoạt động tốt và có thể đọc được cho người khác:

    age = Date.today.year - birthday.year
    age -= 1 if Date.today < birthday + age.years #for days before birthday

Dễ dàng và bạn không cần phải lo lắng về việc xử lý năm nhuận và như vậy.


3
Điều này yêu cầu Rails (cho age.years), nhưng có thể được thực hiện không yêu cầu Rails nếu bạn đã làm một cái gì đó như Date.today.month < birthday.month or Date.today.month == birthday.month && Date.today.mday < birthday.mday.
Chuck

Bạn nói đúng - xin lỗi tôi giả sử Rails vì câu hỏi đã được gắn thẻ. Nhưng có, dễ dàng sửa đổi chỉ cho Ruby.
PJ.

1
Lúc đầu tôi đã chọn cái này vì nó đẹp nhất, nhưng trong sản xuất, nó thường sai, vì những lý do tôi không hiểu. Cái trên sử dụng Time.now.utc.to_date dường như hoạt động tốt hơn.
Kevin

@Kevin Thú vị. Bản thân tôi chưa bao giờ gặp vấn đề gì nhưng điều đó không có nghĩa là không có. Bạn có thể cho tôi một ví dụ cụ thể? Tôi muốn biết nếu có một lỗi. Cảm ơn.
PJ.

2
@sigvei - đó là một tính năng, không phải là lỗi;) Ở hầu hết các quốc gia, kể cả Hoa Kỳ, ngày 28 được coi là hợp pháp trong ngày sinh nhật của bạn trong một năm không nhảy vọt nếu bạn là một đứa trẻ nhảy vọt. Người thực sự sẽ được coi là 10.
PJ.

33

Dùng cái này:

def age
  now = Time.now.utc.to_date
  now.year - birthday.year - (birthday.to_date.change(:year => now.year) > now ? 1 : 0)
end

41
Điều này sẽ phá vỡ nếu birthday.to_date là một năm nhuận và năm hiện tại thì không. Không phải là một sự cố lớn, nhưng nó đã gây ra cho tôi nhiều vấn đề.
philnash

1
Hạ thấp để khuyến khích câu trả lời của philnash.
Nick Sonneveld

2
Một lý do khác để thích câu trả lời của philnash , là nó hoạt động với Ruby cũ, trong khi câu trả lời được chấp nhận chỉ hoạt động với rails/activesupport.
sheldonh

16

Một lớp lót trong Ruby on Rails (ActiveSupport). Xử lý năm nhuận, giây nhảy vọt và tất cả.

def age(birthday)
  (Time.now.to_s(:number).to_i - birthday.to_time.to_s(:number).to_i)/10e9.to_i
end

Logic từ đây - Tính tuổi trong C #

Giả sử cả hai ngày nằm trong cùng múi giờ, nếu không gọi utc()trước to_s()trên cả hai.


1
(Date.today.to_s(:number).to_i - birthday.to_date.to_s(:number).to_i)/1e4.to_icũng hoạt động
Grant Hutchins

FWIW, nhưng sau đó bảo đảm của tôi về "giây nhuận" sẽ bị vô hiệu. ;-) (FWIW phần 2, dù sao thì Ruby cũng không hỗ trợ "bước nhảy vọt"). :-)
Vikrant Chaudhary

1
Không chắc chắn tại sao tôi nhận được downvote về điều này. Quan tâm để giải thích, downvoters thân yêu?
Vikrant Chaudhary

@vikrantChaudhary Tôi không biết, đó là một câu trả lời tuyệt vời. Đã thử nghiệm và hoạt động.
Hector Ordonez

9
(Date.today.strftime('%Y%m%d').to_i - dob.strftime('%Y%m%d').to_i) / 10000

Vì vậy, điều này về cơ bản tính toán sự khác biệt về số ngày nếu mỗi tháng dài 100 ngày và mỗi năm dài 100 tháng. Điều này sẽ không tạo ra sự khác biệt nếu bạn chỉ giữ phần năm.
Jonathan Allard

6

Các câu trả lời cho đến nay là hơi lạ. Nỗ lực ban đầu của bạn khá gần với cách đúng đắn để làm điều này:

birthday = DateTime.new(1900, 1, 1)
age = (DateTime.now - birthday) / 365.25 # or (1.year / 1.day)

Bạn sẽ nhận được một kết quả phân số, vì vậy hãy thoải mái chuyển đổi kết quả thành một số nguyên với to_i. Đây là một giải pháp tốt hơn vì nó xử lý chính xác chênh lệch ngày là khoảng thời gian được tính bằng ngày (hoặc giây trong trường hợp của lớp Thời gian liên quan) kể từ sự kiện. Sau đó, một phân chia đơn giản theo số ngày trong một năm cho bạn tuổi. Khi tính tuổi theo năm theo cách này, miễn là bạn giữ được giá trị DOB ban đầu, không cần phải có trợ cấp cho những năm nhuận.


birthday = Time.mktime (1960,5,5) mang lại cho tôi ngoài phạm vi (vấn đề kỷ nguyên?)
Andrew Grimm

Vâng, đi vấn đề kỷ nguyên. Tôi đã cập nhật câu trả lời để giải quyết vấn đề này.
Bob Aman

birthday = DateTime.now - 1.yearcho tôi một tuổi 0. Thật không may, chia cho 365,25 là một chút không chính xác.
Samir Talwar

Bạn không thể trừ 1. năm như thế từ một đối tượng DateTime. 1. năm giải quyết đến số giây trong một năm. Các đối tượng DateTime hoạt động dựa trên ngày. Ví dụ: (DateTime.now - 365.25) .strftime ("% D") Về độ chính xác, nếu bạn thực sự chỉ giao dịch với ngày sinh nhật, điều đó rất chính xác. Thực tế của vấn đề là, mọi người đã khá thiếu chính xác khi nói đến tuổi. Chúng ta được sinh ra vào một thời điểm chính xác, nhưng chúng ta thường không đưa ra giờ, phút và giây chính xác khi sinh ra khi chúng ta viết ra DOB của mình. Lập luận của tôi ở đây là bạn thực sự không muốn thực hiện phép tính này bằng tay.
Bob Aman

1
Điều này không hiệu quả với những người sinh trước năm 1900. Ví dụ, Gertrude Baines được báo cáo là có tuổi 114,9979 vào ngày sinh nhật của cô vào năm 2009.
Andrew Grimm

6

Đề xuất của tôi:

def age(birthday)
    ((Time.now - birthday.to_time)/(60*60*24*365)).floor
end

Thủ thuật là thao tác trừ với Thời gian trả về giây


Điều này gần như đúng. Năm nhuận có nghĩa là một năm thực sự dài 365,25 ngày. Điều đó cũng có nghĩa là tốt nhất, phương pháp này có thể không tăng tuổi của bạn cho đến 18 giờ vào ngày sinh nhật của bạn.
Ryan Lue

5

Tôi thích cái này:

now = Date.current
age = now.year - dob.year
age -= 1 if now.yday < dob.yday

Nếu bạn nghĩ rằng đây là một ứng cử viên hợp lý cho câu hỏi 3yo đã có 10 câu trả lời khác, bạn nên đưa vào nhiều lý do hơn là sở thích cá nhân. Nếu không, bạn sẽ không nhận được nhiều sự chú ý
John Dvorak

1
Điều này sẽ phá vỡ khi một năm là một năm nhuận và một năm khác thì không
artm 23/2/13

Nếu cuối cùng bạn có 29 ngày, tính toán này sẽ thất bại
phil88530

5

Câu trả lời này là tốt nhất, thay vào đó hãy nâng cao nó.


Tôi thích giải pháp của @ philnash, nhưng điều kiện có thể là vi khuẩn. Điều mà biểu thức boolean làm là so sánh các cặp [tháng, ngày] bằng cách sử dụng thứ tự từ điển , vì vậy người ta chỉ có thể sử dụng so sánh chuỗi của ruby ​​thay thế:

def age(dob)
  now = Date.today
  now.year - dob.year - (now.strftime('%m%d') < dob.strftime('%m%d') ? 1 : 0)
end

1
Thế còn (Date.today.strftime('%Y%m%d').to_i - dob.strftime('%Y%m%d').to_i) / 10000?
Ryan Lue

wow, nó gọn gàng hơn nhiều. bạn nên trả lời và thu thập phần thưởng!
artm

hmm, thực sự tôi thấy đã có câu trả lời này ngay cả trước khi tôi
artm

Ồ, bây giờ tôi cũng vậy ... -_- '
Ryan Lue

4

Đây là một chuyển đổi của câu trả lời này (nó đã nhận được rất nhiều phiếu bầu):

# convert dates to yyyymmdd format
today = (Date.current.year * 100 + Date.current.month) * 100 + Date.today.day
dob = (dob.year * 100 + dob.month) * 100 + dob.day
# NOTE: could also use `.strftime('%Y%m%d').to_i`

# convert to age in years
years_old = (today - dob) / 10000

Nó chắc chắn là độc nhất trong cách tiếp cận của nó nhưng có ý nghĩa hoàn hảo khi bạn nhận ra những gì nó làm:

today = 20140702 # 2 July 2014

# person born this time last year is a 1 year old
years = (today - 20130702) / 10000

# person born a year ago tomorrow is still only 0 years old
years = (today - 20130703) / 10000

# person born today is 0
years = (today - 20140702) / 10000  # person born today is 0 years old

# person born in a leap year (eg. 1984) comparing with non-leap year
years = (20140228 - 19840229) / 10000 # 29 - a full year hasn't yet elapsed even though some leap year babies think it has, technically this is the last day of the previous year
years = (20140301 - 19840229) / 10000 # 30

# person born in a leap year (eg. 1984) comparing with leap year (eg. 2016)
years = (20160229 - 19840229) / 10000 # 32

Chỉ cần nhận ra đây là cùng một anser
br3nt

1

Vì Ruby on Rails được gắn thẻ, đá quý dotiw ghi đè Rails tích hợp distance_of_times_in_words và cung cấp distance_of_times_in_words_hash có thể được sử dụng để xác định tuổi. Năm nhuận được xử lý tốt đối với phần năm mặc dù lưu ý rằng ngày 29 tháng 2 có ảnh hưởng đến phần ngày đảm bảo sự hiểu biết nếu mức độ chi tiết đó là cần thiết. Ngoài ra, nếu bạn không thích cách dotiw thay đổi định dạng của distance_of_time_in_words, hãy sử dụng tùy chọn: mơ hồ để trở lại định dạng ban đầu.

Thêm dotiw vào Gemfile:

gem 'dotiw'

Trên dòng lệnh:

bundle

Bao gồm DateHelper trong mô hình thích hợp để có quyền truy cập vào distance_of_time_in_words và distance_of_time_in_words_hash. Trong ví dụ này, mô hình là 'Người dùng' và trường sinh nhật là 'sinh nhật.

class User < ActiveRecord::Base
  include ActionView::Helpers::DateHelper

Thêm phương thức này vào cùng một mô hình.

def age
  return nil if self.birthday.nil?
  date_today = Date.today
  age = distance_of_time_in_words_hash(date_today, self.birthday).fetch("years", 0)
  age *= -1 if self.birthday > date_today
  return age
end

Sử dụng:

u = User.new("birthday(1i)" => "2011", "birthday(2i)" => "10", "birthday(3i)" => "23")
u.age

1

Tôi tin rằng đây là chức năng tương đương với câu trả lời của @ philnash, nhưng IMO dễ hiểu hơn.

class BirthDate
  def initialize(birth_date)
    @birth_date = birth_date
    @now = Time.now.utc.to_date
  end

  def time_ago_in_years
    if today_is_before_birthday_in_same_year?
      age_based_on_years - 1
    else
      age_based_on_years
    end
  end

  private

  def age_based_on_years
    @now.year - @birth_date.year
  end

  def today_is_before_birthday_in_same_year?
    (@now.month < @birth_date.month) || ((@now.month == @birth_date.month) && (@now.day < @birth_date.day))
  end
end

Sử dụng:

> BirthDate.new(Date.parse('1988-02-29')).time_ago_in_years
 => 31 

0

Những điều sau đây có vẻ hiệu quả (nhưng tôi đánh giá cao nếu nó được kiểm tra).

age = now.year - bday.year
age -= 1 if now.to_a[7] < bday.to_a[7]

0

Nếu bạn không quan tâm đến một hoặc hai ngày, điều này sẽ ngắn hơn và khá tự giải thích.

(Time.now - Time.gm(1986, 1, 27).to_i).year - 1970

0

Ok những gì về điều này:

def age
  return unless dob
  t = Date.today
  age = t.year - dob.year
  b4bday = t.strftime('%m%d') < dob.strftime('%m%d')
  age - (b4bday ? 1 : 0)
end

Điều này giả sử chúng ta đang sử dụng đường ray, gọi agephương thức trên một mô hình và mô hình có một cột cơ sở dữ liệu ngày dob. Điều này khác với các câu trả lời khác vì phương pháp này sử dụng các chuỗi để xác định xem chúng tôi có trước sinh nhật năm nay không.

Ví dụ: nếu doblà 2004/2/28 và todaylà 2014/2/28, agesẽ là 2014 - 2004hoặc 10. Những chiếc phao sẽ 02280229. b4bdaysẽ "0228" < "0229"hoặc true. Cuối cùng, chúng tôi sẽ trừ 1từ agevà nhận được 9.

Đây sẽ là cách bình thường để so sánh hai lần.

def age
  return unless dob
  t = Date.today
  age = today.year - dob.year
  b4bday = Date.new(2016, t.month, t.day) < Date.new(2016, dob.month, dob.day)
  age - (b4bday ? 1 : 0)
end

Điều này hoạt động như nhau, nhưng b4bdaydòng quá dài. Các 2016năm cũng là không cần thiết. Việc so sánh chuỗi ở đầu là kết quả.

Bạn cũng có thể làm điều này

Date::DATE_FORMATS[:md] = '%m%d'

def age
  return unless dob
  t = Date.today
  age = t.year - dob.year
  b4bday = t.to_s(:md) < dob.to_s(:md)
  age - (b4bday ? 1 : 0)
end

Nếu bạn không sử dụng đường ray, hãy thử điều này

def age(dob)
  t = Time.now
  age = t.year - dob.year
  b4bday = t.strftime('%m%d') < dob.strftime('%m%d')
  age - (b4bday ? 1 : 0)
end

👍🏼


Chỉ cần fyi, câu trả lời của bạn là hợp lý như của philnash. Câu trả lời của anh ấy sạch sẽ hơn nhiều và không dựa vào Rails.
Thần chú

@Mantas Philnash cho biết ông đã sử dụng phương pháp đó trong một dự án Rails. Bạn đã có đường ray trong các thẻ. Phương pháp của anh ta có hai so sánh nhiều hơn so với của tôi. Dòng cuối cùng của phương pháp của anh là khó hiểu.
Cruz Nunez

xin lỗi, nhưng mã của philnash sạch hơn mã của bạn rất nhiều. Ngoài ra, mã của anh ấy là một phương pháp đơn giản. Bạn phụ thuộc vào việc có giá trị "dob". Mà có thể không rõ ràng cho người dùng mới. Và thậm chí không giúp mã nhiều. Vâng, mẫu cuối cùng của bạn được loại bỏ nó. Nhưng philnash chỉ là một đoạn mã hoàn hảo, đơn giản và ngu ngốc.
Thần chú

0

Tôi nghĩ sẽ tốt hơn nhiều nếu không tính tháng, bởi vì bạn có thể có được ngày chính xác của một năm bằng cách sử dụng Time.zone.now.yday.

def age
  years  = Time.zone.now.year - birthday.year
  y_days = Time.zone.now.yday - birthday.yday

  y_days < 0 ? years - 1 : years
end

0

Đến với một biến thể Rails của giải pháp này

def age(dob)
    now = Date.today
    age = now.year - dob.year
    age -= 1 if dob > now.years_ago(age)
    age
end

0

DateHelper chỉ có thể được sử dụng để có được năm

puts time_ago_in_words '1999-08-22'

gần 20 năm


0
  def computed_age
    if birth_date.present?
      current_time.year - birth_date.year - (age_by_bday || check_if_newborn ? 0 : 1)
    else
      age.presence || 0
    end
  end


  private

  def current_time
    Time.now.utc.to_date
  end

  def age_by_bday
    current_time.month > birth_date.month
  end

  def check_if_newborn
    (current_time.month == birth_date.month && current_time.day >= birth_date.day)
  end```

-1
  def birthday(user)
    today = Date.today
    new = user.birthday.to_date.change(:year => today.year)
    user = user.birthday
    if Date.civil_to_jd(today.year, today.month, today.day) >= Date.civil_to_jd(new.year, new.month, new.day)
      age = today.year - user.year
    else
      age = (today.year - user.year) -1
    end
    age
  end

-1
Time.now.year - self.birthdate.year - (birthdate.to_date.change(:year => Time.now.year) > Time.now.to_date ? 1 : 0)

-1

Để tính đến năm nhuận (và giả sử sự hiện diện hỗ trợ kích hoạt):

def age
  return unless birthday
  now = Time.now.utc.to_date
  years = now.year - birthday.year
  years - (birthday.years_since(years) > now ? 1 : 0)
end

years_sincesẽ sửa đổi chính xác ngày để đưa vào tài khoản những năm không có bước nhảy vọt (khi sinh nhật 02-29).


-1

Đây là giải pháp của tôi cũng cho phép tính tuổi vào một ngày cụ thể:

def age on = Date.today
  (_ = on.year - birthday.year) - (on < birthday.since(_.years) ? 1 : 0)
end

-2

Tôi đã phải đối phó với điều này quá, nhưng trong nhiều tháng. Trở nên quá phức tạp. Cách đơn giản nhất tôi có thể nghĩ là:

def month_number(today = Date.today)
  n = 0
  while (dob >> n+1) <= today
    n += 1
  end
  n
end

Bạn có thể làm tương tự với 12 tháng:

def age(today = Date.today)
  n = 0
  while (dob >> n+12) <= today
    n += 1
  end
  n
end

Điều này sẽ sử dụng lớp Date để tăng tháng, sẽ xử lý 28 ngày và năm nhuận, v.v.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.