Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to calculate next birthday given a date of birth?

Tags:

postgresql

Given this schema in a Postgres database:

CREATE TABLE person (
    id serial PRIMARY KEY,
    name text,
    birth_date date,
);

How would I query the table to get the date of each person's next birthday after today?

For example if Bob's birth_date is 2000-06-01 then his next birthday would be 2016-06-01.

Note: I'm not looking for the birth_date + a pre-defined interval but rather the next anniversary of a person's birth.

I've written the equivalent in Python:

def next_birthday(self):
    today = datetime.date.today()
    next_birthday = self.birth_date.replace(year=today.year)
    if next_birthday < today:
        next_birthday = next_birthday.replace(year=today.year + 1)
    return next_birthday

However I'd like to see if Postgres can do this in a more performant way.

like image 293
ghickman Avatar asked May 06 '16 20:05

ghickman


1 Answers

select birth_date,
       cast(birth_date + ((extract(year from age(birth_date)) + 1) * interval '1' year) as date) as next_birthday
from person
where name = 'Bob'

The expression (extract(year from age(birth_date)) + 1) * interval '1' year calculates the age at the next birthday in (complete) years. When adding that to the date of birth, this gives the next birthday.

The cast is necessary to get a real date back, because date + interval returns a timestamp (including a time).

If you remove the where condition, you'll get all "next" birthdays.

You can also get a list of the upcoming birthdays in e.g. the next 30 days using something like this:

select next_birthday,
       next_birthday - current_date as days_until_next
from (
  select birth_date,
         cast(birth_date + ((extract(year from age(birth_date)) + 1) * interval '1' year) as date) as next_birthday
  from person
) as upcoming
where upcoming.next_birthday <= current_date + 30
order by next_birthday;
like image 73
a_horse_with_no_name Avatar answered Nov 04 '22 16:11

a_horse_with_no_name