Uzyskaj wiek osoby w Rubim

125

Chciałbym poznać wiek osoby od daty jej urodzin. now - birthday / 365nie działa, ponieważ niektóre lata mają 366 dni. Wymyśliłem następujący kod:

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

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

Czy istnieje bardziej rubinowy sposób obliczania wieku?

Mantas
źródło
6
Podoba mi się to pytanie, ponieważ podkreśla ideę, że istnieje „więcej Rubiego” i „mniej Rubiego” sposobów robienia rzeczy. Ważne jest nie tylko poprawność logiczna (co można by zrobić kopiując odpowiedź w C #), ale także poprawność stylistyczna. Odpowiedź Adinochestvy dobrze wykorzystuje idiom języka Ruby.
James A. Rosen

Odpowiedzi:

410

Wiem, że spóźniłem się na imprezę tutaj, ale przyjęta odpowiedź okropnie się załamie, gdy spróbujesz obliczyć wiek kogoś urodzonego 29 lutego w roku przestępnym. Dzieje się tak, ponieważ wywołanie birthday.to_date.change(:year => now.year)tworzy nieprawidłową datę.

Zamiast tego użyłem następującego kodu:

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
philnash
źródło
4
Użyj tego, a nie zaznaczonego czekiem, który nie obsługuje lat przestępnych
bgcode
Dlaczego zwracasz 0 || 1 zamiast true|| false?
0112
1
@ alex0112 Ponieważ wynik (0 lub 1) tego co prawda mylącego warunku jest odejmowany od różnicy lat między dniem dzisiejszym a datą urodzenia. Ma na celu ustalenie, czy dana osoba obchodziła urodziny jeszcze w tym roku, a jeśli nie, to ma 1 rok mniej niż różnica między latami.
philnash
2
@andrej now = Date.today działa, ale pamiętaj, że nie obsługuje problemów ze strefą czasową. W Railsach Date.today zwraca datę w oparciu o strefę czasową systemu. ActiveRecord zwraca czas na podstawie strefy czasowej skonfigurowanej przez aplikacje. Jeśli strefa czasowa systemu jest inna niż strefa czasowa aplikacji, w rzeczywistości porównujesz czas z dwóch różnych stref czasowych, co nie byłoby bardzo dokładne.
Ulubiony Onwuemene
Czy to naprawdę najprostsze rozwiązanie?
Marco Prins
50

Uważam, że to rozwiązanie działa dobrze i jest czytelne dla innych osób:

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

Łatwe i nie musisz się martwić o obsługę roku przestępnego i tym podobnych.

PJ.
źródło
3
Wymaga to Railsów (dla age.years), ale może nie wymagać Railsów, jeśli zrobiłeś coś podobnego Date.today.month < birthday.month or Date.today.month == birthday.month && Date.today.mday < birthday.mday.
Chuck
Masz rację - przepraszam, że założyłem Rails, ponieważ zostało nim oznaczone pytanie. Ale tak, łatwo zmodyfikować tylko dla Rubiego.
PJ.
1
Na początku wybrałem ten, ponieważ jest najładniejszy, ale w produkcji często jest zły, z powodów, których nie rozumiem. Wydaje się, że powyższy przy użyciu Time.now.utc.to_date działa lepiej.
Kevin
@Kevin Ciekawe. Sam nigdy nie miałem z tym problemu, ale to nie znaczy, że go nie ma. Czy mógłbyś podać mi konkretny przykład? Chciałbym wiedzieć, czy jest błąd. Dzięki.
PJ.
2
@sigvei - to funkcja, a nie błąd;) W większości krajów, w tym w USA, prawnie uważa się, że 28. urodziny w roku innym niż przestępny, jeśli jesteś dzieckiem przestępnym. Ta osoba rzeczywiście byłaby uznana za 10.
PJ.
33

Użyj tego:

def age
  now = Time.now.utc.to_date
  now.year - birthday.year - (birthday.to_date.change(:year => now.year) > now ? 1 : 0)
end
Sadegh
źródło
41
To zepsuje się, jeśli birthday.to_date jest rokiem przestępnym, a bieżący rok nie. Nie jest to duże wydarzenie, ale sprawia mi problemy.
philnash
1
Głosowanie w dół, aby zachęcić do odpowiedzi Philnasha.
Nick Sonneveld
2
Innym powodem, dla którego warto preferować odpowiedź Philnasha , jest to, że działa ona ze zwykłym starym Rubim, podczas gdy zaakceptowana odpowiedź działa tylko z rails/activesupport.
sheldonh
16

Jedna linijka w Ruby on Rails (ActiveSupport). Obsługuje lata przestępne, sekundy przestępne i inne.

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

Logika z tego miejsca - Oblicz wiek w C #

Zakładając, że obie daty są w tej samej strefie czasowej, jeśli nie zadzwoń utc()wcześniej to_s()w obu.

Vikrant Chaudhary
źródło
1
(Date.today.to_s(:number).to_i - birthday.to_date.to_s(:number).to_i)/1e4.to_iteż działa
Grant Hutchins,
FWIW, ale wtedy moja gwarancja na „sekundy przestępne” zostanie unieważniona. ;-) (FWIW część 2, Ruby i tak nie obsługuje "sekund przestępnych"). :-)
Vikrant Chaudhary
1
Nie jestem pewien, dlaczego otrzymuję w tej sprawie głosy przeciw. Masz ochotę wyjaśnić, drodzy przeciwnicy?
Vikrant Chaudhary
@vikrantChaudhary Nie wiem, to świetna odpowiedź. Przetestowane i działa.
Hector Ordonez
9
(Date.today.strftime('%Y%m%d').to_i - dob.strftime('%Y%m%d').to_i) / 10000
pguardiario
źródło
To w zasadzie oblicza różnicę w dniach, jeśli każdy miesiąc miał 100 dni, a każdy rok - 100 miesięcy. Co nie ma znaczenia, jeśli zachowasz tylko część roczną.
Jonathan Allard
6

Jak dotąd odpowiedzi są trochę dziwne. Twoja pierwotna próba była bardzo bliska właściwej metody:

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

Otrzymasz wynik ułamkowy, więc możesz przekonwertować wynik na liczbę całkowitą za pomocą to_i. Jest to lepsze rozwiązanie, ponieważ prawidłowo traktuje różnicę dat jako okres mierzony w dniach (lub sekundach w przypadku powiązanej klasy czasu) od zdarzenia. Następnie prosty podział przez liczbę dni w roku podaje wiek. Przy obliczaniu wieku w ten sposób, o ile zachowasz pierwotną wartość DOB, nie ma potrzeby uwzględniania lat przestępnych.

Bob Aman
źródło
birthday = Time.mktime (1960,5,5) daje mi poza zakresem (problemy z epoką?)
Andrew Grimm
Tak, idź do kwestii epoki. Zaktualizowałem odpowiedź, aby rozwiązać ten problem.
Bob Aman
birthday = DateTime.now - 1.yeardaje mi wiek 0. Niestety dzielenie przez 365,25 jest trochę nieprecyzyjne.
Samir Talwar
Nie możesz odjąć 1 roku w ten sposób od obiektu DateTime. 1. rok przekłada się na liczbę sekund w roku. Obiekty DateTime działają w oparciu o dni. Na przykład: (DateTime.now - 365.25) .strftime ("% D") Jeśli chodzi o precyzję, jeśli naprawdę masz do czynienia tylko z urodzinami, jest ona bardzo dokładna. Faktem jest, że ludzie są już dość nieprecyzyjni, jeśli chodzi o wiek. Urodziliśmy się w określonym momencie, ale zwykle nie podajemy dokładnej godziny, minuty i sekundy naszego urodzenia, kiedy zapisujemy datę urodzenia. Mój argument jest taki, że naprawdę nie chcesz wykonywać tych obliczeń ręcznie.
Bob Aman
1
To nie działa dla osób urodzonych przed 1900 rokiem. Na przykład Gertrude Baines ma wiek 114,9979 w dniu swoich urodzin w 2009 roku.
Andrew Grimm
6

Moja sugestia:

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

Sztuczka polega na tym, że operacja minus z Time zwraca sekundy

jlebrijo
źródło
To prawie prawda. Lata przestępne oznaczają, że rok ma w rzeczywistości 365,25 dni. Oznacza to również, że w najlepszym przypadku ta metoda może przedłużyć twój wiek do 18 godzin przed urodzinami.
Ryan Lue
5

Podoba mi się ten:

now = Date.current
age = now.year - dob.year
age -= 1 if now.yday < dob.yday
tpalm
źródło
Jeśli uważasz, że jest to rozsądny pretendent do pytania dla 3-latek, które ma już 10 innych odpowiedzi, powinieneś podać więcej powodów niż osobiste preferencje. W przeciwnym razie nie zwrócisz zbytniej uwagi
John Dvorak,
1
to się psuje, gdy jeden rok jest rokiem przestępnym, a inny nie
artm
Jeśli w zeszłym roku luty miało 29 dni, to obliczenie nie powiedzie się
phil88530
5

Ta odpowiedź jest najlepsza, zamiast tego zagłosuj za nią.


Podoba mi się rozwiązanie @ philnash, ale warunek może być zwarty. To wyrażenie boolowskie porównuje pary [miesiąc, dzień] w porządku leksykograficznym , więc zamiast tego można po prostu użyć porównania ciągów ruby'ego:

def age(dob)
  now = Date.today
  now.year - dob.year - (now.strftime('%m%d') < dob.strftime('%m%d') ? 1 : 0)
end
artm
źródło
1
O co chodzi (Date.today.strftime('%Y%m%d').to_i - dob.strftime('%Y%m%d').to_i) / 10000?
Ryan Lue
wow, to dużo schludniej. powinieneś udzielić odpowiedzi i odebrać nagrody!
artm
hmm, właściwie widzę, że ta odpowiedź była już przed moją
artm
Och, teraz ja też ... -_- ”
Ryan Lue
4

To jest konwersja tej odpowiedzi (otrzymała dużo głosów):

# 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

Jest zdecydowanie wyjątkowy w swoim podejściu, ale ma sens, gdy zdasz sobie sprawę, co robi:

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
br3nt
źródło
Właśnie zdałem sobie sprawę, że to ten sam anser
br3nt
1

Ponieważ Ruby on Rails jest otagowany, klejnot dotiw zastępuje wbudowaną w Railsach wartość distance_of_times_in_words i zapewnia distance_of_times_in_words_hash której można użyć do określenia wieku. Lata przestępne są traktowane dobrze w części dotyczącej lat, chociaż należy pamiętać, że 29 lutego ma wpływ na część dni, która gwarantuje zrozumienie, czy taki poziom szczegółowości jest potrzebny. Ponadto, jeśli nie podoba ci się, jak dotiw zmienia format parametru distance_of_time_in_words, użyj opcji: vague, aby powrócić do pierwotnego formatu.

Dodaj dotiw do Gemfile:

gem 'dotiw'

W wierszu poleceń:

bundle

Uwzględnij DateHelper w odpowiednim modelu, aby uzyskać dostęp do distance_of_time_in_words i distance_of_time_in_words_hash. W tym przykładzie model to „Użytkownik”, a pole daty urodzin to „urodziny”.

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

Dodaj tę metodę do tego samego modelu.

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

Stosowanie:

u = User.new("birthday(1i)" => "2011", "birthday(2i)" => "10", "birthday(3i)" => "23")
u.age
mindriot
źródło
1

Uważam, że jest to funkcjonalnie równoważne z odpowiedzią @ philnash, ale IMO jest łatwiejsze do zrozumienia.

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

Stosowanie:

> BirthDate.new(Date.parse('1988-02-29')).time_ago_in_years
 => 31 
Jason Swett
źródło
0

Poniższe wydaje się działać (ale byłbym wdzięczny, gdyby został sprawdzony).

age = now.year - bday.year
age -= 1 if now.to_a[7] < bday.to_a[7]
testr
źródło
0

Jeśli nie obchodzi Cię dzień lub dwa, byłoby to krótsze i dość oczywiste.

(Time.now - Time.gm(1986, 1, 27).to_i).year - 1970
hakunin
źródło
0

Ok co z tym:

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

Zakładamy, że używamy railsów, wywołując agemetodę na modelu, a model ma kolumnę bazy danych z datami dob. Różni się to od innych odpowiedzi, ponieważ ta metoda wykorzystuje ciągi znaków do określenia, czy jesteśmy przed tegorocznymi urodzinami.

Na przykład, jeśli dobto 2004/2/28, a todayto 2014/2/28, ageto będzie 2014 - 2004lub 10. Pływaki będą 0228i 0229. b4bdaybędzie "0228" < "0229"lub true. Na koniec odejmiemy 1od agei otrzymamy 9.

Byłby to normalny sposób porównania dwóch razy.

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

Działa to tak samo, ale b4bdaylinia jest za długa. 2016Roku jest również konieczne. Rezultatem było porównanie ciągów na początku.

Ty też możesz to zrobić

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

Jeśli nie używasz szyn, spróbuj tego

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

👍🏼

Cruz Nunez
źródło
Twoja odpowiedź jest logicznie taka sama jak Philnash. Jego odpowiedź jest dużo czystsza i nie polega jednak na Railsach.
Mantas
@Mantas Philnash powiedział, że użył tej metody w projekcie Rails. Miałeś szyny w metkach. Jego metoda ma dwa porównania więcej niż moja. Ostatnia linia jego metody jest trudna do zrozumienia.
Cruz Nunez,
przepraszam, ale kod philnasha jest znacznie czystszy niż twój. Ponadto jego kod jest prostą metodą. Twoja zależy od wartości „dob”. Co może nie być takie jasne dla nowych użytkowników. I nawet nie pomaga zbytnio w kodzie. Tak, twoja ostatnia próbka się go pozbędzie. Ale Philnash to po prostu doskonały, prosty i głupi kawałek kodu.
Mantas,
0

Myślę, że o wiele lepiej nie liczyć miesięcy, ponieważ możesz uzyskać dokładny dzień w roku, używając 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
Oleksiy Nosov
źródło
0

Pojawił się z odmianą tego rozwiązania w Railsach

def age(dob)
    now = Date.today
    age = now.year - dob.year
    age -= 1 if dob > now.years_ago(age)
    age
end
derosm2
źródło
0

DateHelper może być używany tylko do pobierania lat

puts time_ago_in_words '1999-08-22'

prawie 20 lat

hsul4n
źródło
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```
Sandesh Bodake
źródło
-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
Brian
źródło
-1
Time.now.year - self.birthdate.year - (birthdate.to_date.change(:year => Time.now.year) > Time.now.to_date ? 1 : 0)
Tim Van Ginderen
źródło
-1

Aby uwzględnić lata przestępne (i zakładając obecność aktywnego wsparcia):

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_sincepoprawnie zmodyfikuje datę, aby uwzględnić lata inne niż przestępne (kiedy są urodziny 02-29).

Witalij Kushner
źródło
-1

Oto moje rozwiązanie, które pozwala również na obliczenie wieku w określonym dniu:

def age on = Date.today
  (_ = on.year - birthday.year) - (on < birthday.since(_.years) ? 1 : 0)
end
hurikhan77
źródło
-2

Ja też musiałem sobie z tym poradzić, ale od miesięcy. Stało się zbyt skomplikowane. Najprościej przyszło mi do głowy:

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

Możesz zrobić to samo przez 12 miesięcy:

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

Spowoduje to użycie klasy Date do zwiększenia miesiąca, który zajmie 28 dni, rok przestępny itp.

Mooktakim Ahmed
źródło