Перейти к содержанию

412 уроков, 25 библиотек и челлендж «Что выведет код?» — бесплатно, код прямо в браузере

Начать обучение
Урок 16 из 20 Начальный 20 мин 120 XP

Возраст и разница в днях

Полный возраст из даты рождения с учётом того, был ли день рождения в этом году, дни до события и недели из timedelta — три расчёта, которые нужны в любом проекте с датами.

Редакция Питоники

calendar из прошлого урока рисовал месяцы, но не отвечал на житейские вопросы: сколько человеку лет, сколько дней осталось до экзамена, на какую неделю выпадает поездка. Всё это — разница дат, и инструменты для неё уже есть: вычитание из урока про timedelta и сравнения из урока про сортировку. Осталось собрать из них два расчёта, которые встречаются чаще остальных.

Полный возраст: не просто разница годов

Первый рефлекс — вычесть годы рождения. Смотрим, что получается, и нарочно берём человека, у которого день рождения в декабре, а на дворе октябрь:

наивный возраст врёт
from datetime import date

birth = date(1990, 12, 5)
today = date(2026, 10, 21)  # фиксированная дата вместо даты из системы

print(today.year - birth.year)
Вывод
36

Программа говорит «36», но человеку в этот день 36 лет ещё не исполнилось: 5 декабря только предстоит. Возраст — это не разница годов, а разница годов со скидкой на ещё не наступивший день рождения. Скидка применяется одним сравнением: если пара (месяц, день) сегодняшней даты меньше пары дня рождения — день рождения в этом году ещё не был, и из разности годов нужно вычесть единицу.

полный возраст через кортежи
from datetime import date

def full_age(birth, today):
    age = today.year - birth.year
    if (today.month, today.day) < (birth.month, birth.day):
        age -= 1
    return age

today = date(2026, 10, 21)  # фиксированная дата вместо даты из системы

print(full_age(date(1990, 7, 14), today))
print(full_age(date(1990, 12, 5), today))
Вывод
36
35

Оба ответа теперь верны. Родившемуся 14 июля день рождения в 2026-м уже был — 14 июля меньше 21 октября, вычитания нет, честные 36. Родившемуся 5 декабря — ещё нет: кортеж (10, 21) меньше (12, 5), минус один, честные 35. Кортежи сравниваются поэлементно: сначала месяцы, и только при равенстве — дни. Поэтому (10, 21) < (10, 25) тоже сработает правильно для дней рождения в том же месяце.

Дни до события: вычитание даёт timedelta

Обратная задача — не возраст позади, а срок впереди. Вычитание двух date возвращает timedelta, и у него сразу есть ответ:

сколько до Нового года
from datetime import date

today = date(2026, 10, 21)  # фиксированная «сегодня» для примера
event = date(2026, 12, 31)

delta = event - today
print(delta)
print(delta.days)
Вывод
71 days, 0:00:00
71

timedelta печатается целиком — дни, часы, минуты, секунды, — но для дат время всегда нулевое, и работать удобно с полем days: целое число суток. До Нового года — 71 день. Порядок вычитания важен: событие минус сегодня даёт положительное число, сегодня минус событие — отрицательное.

Сколько это недель: timedelta // timedelta

«71 день» — много, «до экзамена десять недель» — понятнее. timedelta делится на timedelta, и целочисленное деление отсекает неполные недели:

полные недели и остаток
from datetime import date, timedelta

today = date(2026, 10, 21)  # фиксированная «сегодня» для примера
exam = date(2027, 1, 15)

delta = exam - today
print(delta.days)
print(delta // timedelta(days=7))
print(delta.days % 7)
Вывод
86
12
2

До сессии 15 января — 86 дней. Деление delta // timedelta(days=7) возвращает целое: 12 полных недель, а остаток delta.days % 7 — ещё 2 дня. Та же пара операций даёт «месяцев с округлением вниз» через timedelta(days=30), хотя месяцы разной длины и для них точного деления не существует.

До ближайшего дня рождения

Соединим оба приёма: полный возраст подсказывает, был ли день рождения в этом году, а replace переносит дату на нужный год — чтобы посчитать, сколько ждать следующего:

дни до ближайшего дня рождения
from datetime import date

birth = date(1990, 7, 14)
today = date(2026, 10, 21)  # фиксированная «сегодня» для примера

next_bd = birth.replace(year=today.year)
if next_bd < today:
    next_bd = birth.replace(year=today.year + 1)

print(next_bd)
print((next_bd - today).days)
Вывод
2027-07-14
266

День рождения 14 июля 2026-го уже прошёл, поэтому replace сдвигает дату на 2027-й — и до праздника 266 дней. replace не двигает месяцы и дни, меняет только год, а сравнение next_bd < today решает, тот ли это год.

ВыражениеЧто даёт
d2 - d1timedelta — промежуток между датами
delta.daysцелое число суток
delta // timedelta(days=7)полных недель
delta.days % 7остаток в днях
d.replace(year=2027)копия даты с другим годом

Что дальше

Возраст и дни до события считают календарные сутки — все подряд. Следующий урок считает иначе: рабочие дни без выходных, где timedelta шагает по датам в цикле, а weekday() отсекает субботы и воскресенья.

Возраст — это не разница годов, а разница годов со скидкой на ещё не наступивший день рождения.

Включать конец периода или нет

Отдельная головная боль, из-за которой в отчётности сходятся не всегда: разность дат не включает второй конец. Между 1-м и 31-м октября 2026 года тридцать суток — но если речь о днях, которые вы провели в поездке, их было тридцать один: и первый, и последний день считаются. Добавить единицу — дело одной строки, но решение надо принять один раз и записать в документацию.

дни с обоим концом и без
from datetime import date

def days_between(a, b, inclusive=False):
    delta = (b - a).days
    return delta + 1 if inclusive else delta

print(days_between(date(2026, 10, 1), date(2026, 10, 31)))
print(days_between(date(2026, 10, 1), date(2026, 10, 31), inclusive=True))
Вывод
30
31

Мы договорились: функция по умолчанию считает разность, то есть промежуток между двумя датами, а параметр inclusive добавляет единицу для случаев «сколько дней заняло». Для срока до события мы оставили разность без флага — «осталось 71 день» читается однозначно. Но когда этот же подсчёт пошёл в акт о выполнении работ, там понадобились оба конца, и пришлось вводить явный параметр, а не молча прибавлять единицу в трёх местах.

Возраст в месяцах, а не только в годах

«36 лет» — грубое округление, и для медицинских карт, договоров и подарков оно иногда не годится. Полный возраст с точностью до месяца считается чуть сложнее: сначала целые годы, потом якорная дата — день рождения текущего года, — а от неё уже отсчитываются месяцы.

возраст в годах и месяцах
from datetime import date

def exact_age(birth, today):
    years = today.year - birth.year
    anchor = birth.replace(year=birth.year + years)
    if anchor > today:
        years -= 1
        anchor = birth.replace(year=birth.year + years)
    months = (today.year - anchor.year) * 12 + (today.month - anchor.month)
    if today.day < anchor.day:
        months -= 1
    return years, months

print(exact_age(date(1990, 7, 14), date(2026, 10, 21)))
print(exact_age(date(1990, 12, 5), date(2026, 10, 21)))
print(exact_age(date(2010, 10, 21), date(2026, 10, 21)))
Вывод
(36, 3)
(35, 10)
(16, 0)

Логика функции: сначала предполагаем, что годов уже столько, сколько разница лет, и строим якорь — день рождения этого года. Если якорь ещё впереди, значит, год не наступил, отнимаем единицу и пересобираем якорь. Дальше месяцы считаются как разница номеров месяцев, а если день рождения в этом месяце ещё не наступил, отнимается ещё один месяц. Родившемуся 14 июля 1990 года на 21 октября 2026 года честные 36 лет и 3 месяца; родившемуся 5 декабря — 35 лет и 10 месяцев; а тем, кто родился ровно 21 октября 2010 года, сегодня ровно 16 лет и ноль месяцев.

проверка возраста обратным ходом
from datetime import date

def full_age(birth, today):
    age = today.year - birth.year
    if (today.month, today.day) < (birth.month, birth.day):
        age -= 1
    return age

birth = date(1990, 7, 14)
today = date(2026, 10, 21)
age = full_age(birth, today)

print(age)
print(birth.replace(year=birth.year + age))
Вывод
36
2026-07-14

Возраст в днях тоже иногда нужен — например, чтобы понять, через сколько дней человек отметит следующую круглую дату, или для расчёта стажа. Тут разность дат работает напрямую: между 14 июля 1990 года и 21 октября 2026 года прошло 13 248 суток, а делением на 365 получается 36 полных лет с небольшой поправкой на високосные годы.

возраст в днях
from datetime import date

birth = date(1990, 7, 14)
today = date(2026, 10, 21)

print((today - birth).days)
print((today - birth).days // 365)
Вывод
13248
36

Число 36 снова на месте, но по другой дороге: 13 248 дней, разделённых на 365, дают 36 целых лет с остатком. Расхождение с функцией полного возраста возникает в високосные годы — 365 в делителе не учитывает 29 февраля. Для «сколько дней прошло» разность идеальна, для «сколько лет» — нет, поэтому мы не заменяем ею функцию возраста.

Месяцы — приблизительно, и это нормально

Кто-то спрашивает: сколько месяцев осталось до экзамена. Ответ «12 месяцев» звучит уверенно, но месяцы разной длины, и деление на 30 даёт приблизительный результат. Мы всё равно считаем так — потому что точность здесь не нужна, а вот привычка считать в днях спасает.

недели, месяцы и дни одним делением
from datetime import date, timedelta

today = date(2026, 10, 21)
exam = date(2027, 1, 15)
delta = exam - today

print(delta.days)
print(delta // timedelta(days=7))
print(delta // timedelta(days=30))
print(delta.days % 7)
Вывод
86
12
2
2

До сессии 86 дней. Это 12 полных недель и остаток в 2 дня. Деление на timedelta(days=30) даёт 2 целых «месяца» по 30 дней — и это честное приближение, а не ошибка: месяц в календаре не имеет фиксированной длины, поэтому точного ответа не существует в принципе. Для планирования двух месяцев на подготовку такой ответ годится, для расчёта даты платежа — нет, там нужна конкретная дата.

День рождения 29 февраля подробнее

Питфолл про replace из урока стоит разобрать до конца, потому что на нём ломается не только replace. Человек, родившийся 29 февраля, празднует день рождения раз в четыре года — но только в високосные годы. Любая функция, которая пытается построить дату рождения в текущем году через replace, на невисокосном году упадёт.

что происходит с 29 февраля
from datetime import date

birth = date(2000, 2, 29)
today = date(2026, 10, 21)

try:
    print(birth.replace(year=today.year))
except ValueError as e:
    print('ValueError:', e)

celebrated = birth.replace(year=today.year - 1, day=28)
print(celebrated)
Вывод
ValueError: day is out of range for month
2025-02-28

Два выхода, и оба требуют договорённости с заказчиком. Первый: праздник считается 28 февраля — как в России, где день рождения 29 февраля отмечают 28-го, в високосный год — 29-го. Второй: 1 марта — так принято во многих странах Запада. Третий вариант, когда день рождения просто не наступил в этом году и его отмечают 28 февраля будущего високосного, встречается реже. Ключевое: решение фиксируется в коде явно, а не через try/except, который молча ловит ошибку и продолжает.

ВыражениеЧто даёт
d2 - d1timedelta — промежуток между датами
delta.daysцелое число суток
delta // timedelta(days=7)полных недель
delta.days % 7остаток в днях
d.replace(year=2027)копия даты с другим годом
delta // timedelta(days=30)приблизительные месяцы по 30 дней
(b - a).days + 1дни с обоим концом периода

Семь выражений, которые закрывают почти все бытовые вопросы про даты. Первые четыре — про промежуток, последние три — про его представление в других единицах и про то, как считать границы. Всё это работает и для object-ов datetime, и для date: единственное, что нужно помнить, — не смешивать типы, потому что date минус datetime падает с TypeError, о чём подробнее в уроке про combine и replace.

Что выведет код?

Сначала предскажи ответ в голове — это главный навык программиста.

from datetime import date

a = date(2026, 10, 21)
b = date(2026, 5, 1)
print((a - b).days)
from datetime import date, timedelta

trip = date(2026, 11, 8)
today = date(2026, 10, 21)
delta = trip - today
print(delta // timedelta(days=7))
from datetime import date

today = date(2026, 10, 21)
birth = date(1988, 10, 21)
print(today.year - birth.year)
from datetime import date

a = date(2026, 10, 1)
b = date(2026, 10, 31)
print((b - a).days)
print((b - a).days + 1)
from datetime import date

birth = date(1990, 7, 14)
today = date(2026, 10, 21)
print((today - birth).days // 365)
Проверь себя
0 / 9

1. Почему today.year - birth.year — неверный возраст?

2. Что вернёт сравнение (10, 21) < (12, 5)?

3. Какой тип у результата выражения event - today, где обе переменные — date?

4. Что делает delta // timedelta(days=7)?

5. birth = date(2000, 2, 29). Что произойдёт при вызове birth.replace(year=2026)?

6. Сколько дней между 1 и 31 октября 2026 года вернёт (b - a).days?

7. Что вернёт delta // timedelta(days=30) для промежутка в 86 дней?

8. Как посчитать полный возраст в годах и месяцах?

9. Почему возраст в днях, посчитанный как (today - birth).days // 365, может отличаться от полного возраста?

Карточки терминов
Запомнено: 0 / 10
Практика

Посчитайте для Анны, родившейся 8 марта 2001 года, её полный возраст на 21 октября 2026 — с корректировкой по кортежам месяцев и дней. Затем выведите, сколько дней осталось до сдачи сессии 5 января 2027 и сколько это полных недель. Три числа, каждое на своей строке.

practice.py
Вопросы и ответы по уроку

Как посчитать возраст по дате рождения в Python?

Вычтите годы, затем скорректируйте результат: если пара (месяц, день) сегодняшней даты меньше пары дня рождения — день рождения в этом году ещё не был, и из разности вычитается единица. Сравнение удобно делать кортежами: (today.month, today.day) < (birth.month, birth.day). Именно так работает full_age из этого урока.

Урок 16: возраст и разница в днях

Как узнать количество дней между двумя датами в Python?

Вычесть одну date из другой: (date(2026, 12, 31) - date(2026, 10, 21)).days вернёт 71. Результат вычитания — timedelta, поле days хранит целые сутки. Порядок важен: событие минус сегодня даёт положительное число дней до события.

Как перевести timedelta в недели или часы?

Делением timedelta на timedelta: delta // timedelta(days=7) — полные недели, delta // timedelta(hours=1) — часы. Обычное деление delta / timedelta(days=1) даёт дробное число дней. Умножение timedelta(days=1) * 7 наоборот строит промежуток из числа.

Что делать с днём рождения 29 февраля?

Полный возраст кортежным сравнением считает его без проблем. Но перенос даты в текущий год через replace(year=...) упадёт с ValueError в невисокосные годы, потому что 29 февраля не существует. Ловите ValueError и решайте по правилу проекта: считать такой день рождения 28 февраля или 1 марта.

Как посчитать количество дней между двумя датами включительно?

Разность (b - a).days даёт промежуток: между 1 и 31 октября 2026 года это 30 суток. Чтобы получить число дней с обоим концом, прибавьте единицу — получится 31. Мы вынесли это в отдельный параметр inclusive, потому что для «сколько дней до события» единица, наоборот, лишняя.

Как посчитать возраст в годах и месяцах?

Двухшагово: сначала целые годы с корректировкой по дню рождения (полный_age из урока), затем от якоря — день рождения текущего года — считаются месяцы, и если день рождения в этом месяце ещё не наступил, отнимается ещё один месяц. Для 14 июля 1990 года на 21 октября 2026 года получится 36 лет и 3 месяца.

Урок 16: возраст и разница в днях

Можно ли посчитать возраст как (сегодня - дата рождения).days // 365?

Для грубой прикидки — да, и обычно получается верно. Но точного ответа не будет: в делителе нет поправки на високосные годы, которых за 36 лет набирается восемь или девять. Для точного возраста нужна функция с проверкой дня рождения — та, что в уроке.

Сколько месяцев осталось до события — как посчитать?

Делением на timedelta(days=30): delta // timedelta(days=30) даёт целое число приблизительных месяцев. Это именно приближение — месяцы в календаре разной длины, и точного ответа не существует. Для планирования годится, для расчёта конкретной даты платежа — нет, там нужна дата.

Понравился урок? Сошлитесь на него

«Возраст — это не разница годов, а разница годов со скидкой на ещё не наступивший день рождения.»

Скопируйте готовую ссылку в формате HTML, Markdown или чистый адрес и вставьте в статью на Habr, VC, Telegram-канал или свой блог — так о проекте узнают новые читатели.

TelegramVK

Что дальше

Осталось в разделе: 20 из 20. Прогресс хранится в вашем браузере.

Похожие уроки по темам

Подобраны автоматически по пересечению тем и ключевых слов.