Dapatkan umur seseorang di Ruby


125

Saya ingin mendapatkan umur seseorang dari hari ulang tahunnya. now - birthday / 365tidak bekerja, karena beberapa tahun memiliki 366 hari. Saya datang dengan kode berikut:

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

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

Apakah ada cara yang lebih Ruby untuk menghitung usia?


6
Saya suka pertanyaan ini karena menyoroti gagasan bahwa ada cara "lebih banyak Ruby" dan "kurang Ruby" dalam melakukan sesuatu. Sangat penting untuk tidak hanya benar secara logis (yang bisa Anda lakukan dengan menyalin jawaban C #), tetapi juga benar secara gaya. Dan jawaban Adinochestva memanfaatkan idiom Ruby dengan baik.
James A. Rosen

Jawaban:


410

Saya tahu saya terlambat ke pesta di sini, tetapi jawaban yang diterima akan pecah ketika mencoba mencari tahu usia seseorang yang lahir pada tanggal 29 Februari pada tahun kabisat. Ini karena panggilan untuk birthday.to_date.change(:year => now.year)membuat tanggal yang tidak valid.

Saya menggunakan kode berikut sebagai gantinya:

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
Gunakan ini, bukan tanda centang yang tidak dapat menangani tahun kabisat
bgcode

Mengapa Anda mengembalikan 0 || 1 bukannya true|| false?
0112

1
@ alex0112 Karena hasil (0 atau 1) dari yang diakui kondisional membingungkan dikurangi dari perbedaan tahun antara sekarang dan tanggal lahir. Hal ini dimaksudkan untuk mengetahui apakah orang tersebut telah berulang tahun tahun ini dan jika tidak, mereka 1 tahun lebih muda dari selisih tahun.
philnash

2
@andrej sekarang = Date.today berfungsi tetapi perhatikan bahwa ia tidak menangani masalah dengan Timezone. Di Rails Date.today mengembalikan tanggal berdasarkan zona waktu sistem. ActiveRecord mengembalikan waktu berdasarkan zona waktu yang dikonfigurasi Aplikasi Anda. Jika zona waktu sistem berbeda dari zona waktu aplikasi Anda, Anda akan secara efektif membandingkan waktu dari dua zona waktu berbeda yang tidak akan sangat akurat.
Favorit Onwuemene

Apakah ini benar-benar solusi paling sederhana?
Marco Prins

50

Saya menemukan solusi ini berfungsi dengan baik dan dapat dibaca oleh orang lain:

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

Mudah dan Anda tidak perlu khawatir tentang menangani tahun kabisat dan semacamnya.


3
Ini membutuhkan Rails (untuk age.years), tetapi bisa dibuat tidak membutuhkan Rails jika Anda melakukan sesuatu seperti Date.today.month < birthday.month or Date.today.month == birthday.month && Date.today.mday < birthday.mday.
Chuck

Anda benar - maaf saya menganggap Rails sebagai pertanyaan yang ditandai dengannya. Tapi ya, mudah dimodifikasi hanya untuk Ruby.
PJ.

1
Saya telah memilih yang ini pada awalnya karena itu yang paling cantik, tetapi dalam produksi, itu sering salah, untuk alasan saya tidak mengerti. Yang di atas menggunakan Time.now.utc.to_date tampaknya berfungsi lebih baik.
Kevin

@Kevin Menarik. Saya sendiri tidak pernah punya masalah dengan ini tetapi itu tidak berarti tidak ada. Bisakah Anda memberi saya contoh spesifik? Saya ingin tahu apakah ada bug. Terima kasih.
PJ.

2
@sigvei - itu adalah fitur, bukan bug;) Di sebagian besar negara, termasuk AS, tanggal 28 secara hukum dianggap sebagai hari ulang tahun Anda di tahun yang tidak kabisat jika Anda bayi kabisat. Orang itu memang akan dianggap 10.
PJ.

33

Gunakan ini:

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

41
Ini rusak jika birthday.to_date adalah tahun kabisat dan tahun ini tidak. Bukan kejadian besar, tapi itu telah menyebabkan masalah bagi saya.
philnash

1
Downvoting untuk mendorong jawaban philnash.
Nick Sonneveld

2
Alasan lain untuk lebih menyukai jawaban philnash , adalah bahwa ia bekerja dengan Ruby tua yang polos, sedangkan jawaban yang diterima hanya bekerja dengan rails/activesupport.
sheldonh

16

Satu liner di Ruby on Rails (ActiveSupport). Menangani tahun kabisat, detik kabisat dan semua.

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

Logika dari sini - Hitung usia dalam C #

Dengan asumsi kedua tanggal berada di zona waktu yang sama, jika tidak menelepon utc()sebelumnya to_s()pada keduanya.


1
(Date.today.to_s(:number).to_i - birthday.to_date.to_s(:number).to_i)/1e4.to_ijuga bekerja
Grant Hutchins

FWIW, tapi kemudian jaminan saya pada "detik kabisat" akan dibatalkan. ;-) (FWIW bagian 2, Ruby toh tidak mendukung "lompatan detik"). :-)
Vikrant Chaudhary

1
Tidak yakin mengapa saya menerima downvotes tentang ini. Mau jelaskan, downvoters sayang?
Vikrant Chaudhary

@ VikrantChaudhary Saya tidak tahu, ini jawaban yang bagus. Diuji dan bekerja.
Hector Ordonez

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

Jadi ini pada dasarnya menghitung perbedaan dalam hari jika setiap bulan adalah 100 hari, dan setiap tahun adalah 100 bulan. Yang tidak membuat perbedaan jika Anda hanya mempertahankan bagian tahun.
Jonathan Allard

6

Jawabannya sejauh ini agak aneh. Upaya awal Anda cukup dekat dengan cara yang benar untuk melakukan ini:

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

Anda akan mendapatkan hasil fraksional, jadi jangan ragu untuk mengubah hasilnya menjadi bilangan bulat to_i. Ini adalah solusi yang lebih baik karena memperlakukan perbedaan tanggal dengan benar sebagai periode waktu yang diukur dalam beberapa hari (atau detik dalam kasus kelas Waktu terkait) sejak peristiwa tersebut. Kemudian pembagian sederhana dengan jumlah hari dalam setahun memberi Anda usia. Saat menghitung usia dalam tahun dengan cara ini, selama Anda mempertahankan nilai DOB asli, tidak perlu ada penyisihan untuk tahun kabisat.


birthday = Time.mktime (1960,5,5) memberi saya di luar jangkauan (masalah zaman?)
Andrew Grimm

Ya, pergi pergi masalah zaman. Saya telah memperbarui jawaban untuk menyelesaikan ini.
Bob Aman

birthday = DateTime.now - 1.yearmemberi saya usia 0. Sayangnya, pembagian dengan 365.25 sedikit tidak tepat.
Samir Talwar

Anda tidak dapat mengurangi 1. tahun seperti itu dari objek DateTime. 1. tahun ditentukan ke jumlah detik dalam setahun. Objek DateTime beroperasi berdasarkan hari. Sebagai contoh: (DateTime.now - 365.25) .strftime ("% D") Adapun presisi, jika Anda benar-benar hanya berurusan dengan ulang tahun, itu jauh lebih tepat. Faktanya adalah, orang sudah cukup tepat ketika datang ke usia. Kita dilahirkan pada saat yang tepat dalam waktu, tetapi biasanya kita tidak memberikan jam, menit, dan detik kelahiran yang tepat ketika kita menuliskan DOB kita. Argumen saya di sini adalah bahwa Anda benar-benar tidak ingin melakukan perhitungan ini secara manual.
Bob Aman

1
Ini tidak berfungsi untuk orang yang lahir sebelum 1900. Misalnya Gertrude Baines dilaporkan memiliki usia 114.9979 pada hari ulang tahunnya pada 2009.
Andrew Grimm

6

Saran saya:

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

Kuncinya adalah bahwa operasi minus dengan Waktu mengembalikan detik


Ini hampir benar. Tahun kabisat berarti satu tahun sebenarnya panjangnya 365,25 hari. Itu juga berarti bahwa yang terbaik, metode ini mungkin tidak menambah usia Anda hingga 18 jam setelah ulang tahun Anda.
Ryan Lue

5

Saya suka yang ini:

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

Jika menurut Anda ini adalah penantang yang masuk akal untuk pertanyaan 3yo yang sudah memiliki 10 jawaban lain, Anda harus memasukkan lebih banyak alasan daripada preferensi pribadi. Jika tidak, Anda tidak akan mendapatkan banyak perhatian
John Dvorak

1
ini pecah ketika satu tahun adalah tahun kabisat dan yang lainnya tidak
artm

Jika feb tahun lalu mendapat 29 hari, perhitungan ini akan gagal
phil88530

5

Jawaban ini adalah yang terbaik, lebih baik pilih itu.


Saya suka solusi @ philnash, tapi syaratnya bisa compacter. Apa yang dilakukan oleh ekspresi boolean adalah membandingkan pasangan [bulan, hari] menggunakan urutan leksikografis , jadi orang dapat menggunakan perbandingan string Ruby sebagai gantinya:

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

1
Bagaimana dengan (Date.today.strftime('%Y%m%d').to_i - dob.strftime('%Y%m%d').to_i) / 10000?
Ryan Lue

wow, jauh lebih rapi. Anda harus membuat jawaban dan mengumpulkan hadiah!
artm

hmm, sebenarnya saya melihat sudah ada jawaban ini bahkan sebelum saya
artm

Oh, sekarang aku juga ... -_- '
Ryan Lue

4

Ini adalah konversi dari jawaban ini (menerima banyak suara):

# 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

Ini jelas unik dalam pendekatannya tetapi sangat masuk akal ketika Anda menyadari apa yang dilakukannya:

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

Baru sadar ini adalah
server yang

1

Karena Ruby on Rails ditandai, permata permata menimpa Rails bawaan distance_of_times_in_words dan menyediakan distance_of_times_in_words_hash yang dapat digunakan untuk menentukan usia. Tahun kabisat ditangani dengan baik untuk porsi tahun meskipun perlu disadari bahwa 29 Februari memang berpengaruh pada porsi hari yang membutuhkan pemahaman jika tingkat perincian diperlukan. Juga, jika Anda tidak suka bagaimana cara mengubah format distance_of_time_in_words, gunakan opsi: vague untuk kembali ke format asli.

Tambahkan Dotiw ke Gemfile:

gem 'dotiw'

Di baris perintah:

bundle

Sertakan DateHelper dalam model yang sesuai untuk mendapatkan akses ke distance_of_time_in_words dan distance_of_time_in_words_hash. Dalam contoh ini modelnya adalah 'Pengguna' dan bidang ulang tahun adalah 'ulang tahun.

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

Tambahkan metode ini ke model yang sama.

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

Pemakaian:

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

1

Saya percaya ini secara fungsional setara dengan jawaban @ philnash, tetapi IMO lebih mudah dimengerti.

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

Pemakaian:

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

0

Berikut ini tampaknya berfungsi (tapi saya akan menghargai jika diperiksa).

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

0

Jika Anda tidak peduli satu atau dua hari, ini akan lebih pendek dan cukup jelas.

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

0

Ok bagaimana dengan ini:

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

Ini dengan asumsi kita menggunakan rel, memanggil agemetode pada model, dan model memiliki kolom basis data tanggal dob. Ini berbeda dari jawaban lain karena metode ini menggunakan string untuk menentukan apakah kita sebelum ulang tahun tahun ini.

Misalnya, jika dob2004/2/28 dan today2014/2/28, ageakan 2014 - 2004atau 10. Float akan menjadi 0228dan 0229. b4bdayakan "0228" < "0229"atau true. Akhirnya, kita akan mengurangi 1dari agedan 9.

Ini akan menjadi cara normal untuk membandingkan dua kali.

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

Ini bekerja sama, tetapi b4bdaygarisnya terlalu panjang. The 2016tahun ini juga tidak perlu. Perbandingan string di awal adalah hasilnya.

Anda juga bisa melakukan ini

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

Jika Anda tidak menggunakan rel, coba ini

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

👍🏼


Hanya fyi, jawaban Anda secara logis sama dengan jawaban philnash. Jawabannya jauh lebih bersih dan tidak bergantung pada Rails.
Mantas

@Mantas Philnash mengatakan dia menggunakan metode itu dalam proyek Rails. Anda memiliki rel di tag. Metodenya memiliki dua perbandingan lebih dari saya. Baris terakhir dari metodenya sulit dipahami.
Cruz Nunez

maaf, tapi kode philnash jauh lebih bersih daripada milikmu. Juga, kodenya adalah metode sederhana. Anda bergantung pada memiliki nilai "dob". Yang mungkin tidak jelas bagi pengguna baru. Dan bahkan tidak banyak membantu kode. Ya, sampel terakhir Anda menghilangkannya. Tapi philnash's hanya potongan kode yang sempurna.
Mantas

0

Saya pikir itu jauh lebih baik untuk tidak menghitung bulan, karena Anda bisa mendapatkan hari dalam setahun dengan menggunakan 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

Datang dengan variasi Rails dari solusi ini

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

0

DateHelper dapat digunakan untuk mendapatkan tahun saja

puts time_ago_in_words '1999-08-22'

hampir 20 tahun


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

Untuk menghitung tahun kabisat (dan dengan asumsi kehadiran dukungan aktif):

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_sinceakan dengan benar mengubah tanggal untuk memperhitungkan tahun non-kabisat (saat ulang tahun 02-29).


-1

Inilah solusi saya yang juga memungkinkan penghitungan usia pada tanggal tertentu:

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

-2

Saya harus berurusan dengan ini juga, tetapi selama berbulan-bulan. Menjadi terlalu rumit. Cara paling sederhana yang bisa saya pikirkan adalah:

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

Anda dapat melakukan hal yang sama dengan 12 bulan:

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

Ini akan menggunakan kelas Date untuk meningkatkan bulan, yang akan menangani 28 hari dan tahun kabisat dll.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.