Возраст и разница в днях
Полный возраст из даты рождения с учётом того, был ли день рождения в этом году, дни до события и недели из 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 - d1 | timedelta — промежуток между датами |
| 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, на невисокосном году упадёт.
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 - d1 | timedelta — промежуток между датами |
| 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)
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, может отличаться от полного возраста?
Посчитайте для Анны, родившейся 8 марта 2001 года, её полный возраст на 21 октября 2026 — с корректировкой по кортежам месяцев и дней. Затем выведите, сколько дней осталось до сдачи сессии 5 января 2027 и сколько это полных недель. Три числа, каждое на своей строке.
Как посчитать возраст по дате рождения в 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-канал или свой блог — так о проекте узнают новые читатели.
Что дальше
Осталось в разделе: 20 из 20. Прогресс хранится в вашем браузере.
Что читать дальше
Datetime · Урок 15
calendar: месяц и год в текст
Модуль calendar рисует месяц одной строкой, говорит, каким днём недели была дата, проверяет год на високосность и сообщает размер месяца — без единого объекта datetime.
Datetime · Урок 17
Рабочие дни: будни без выходных
Перебираем даты циклом с timedelta, отсекаем выходные условием weekday() < 5 и отвечаем на два вопроса планировщика: сколько будней между датами и на какое число выпадает срок через N рабочих дней.
Похожие уроки по темам
Подобраны автоматически по пересечению тем и ключевых слов.
openpyxl · Урок 9
Даты в Excel через openpyxl: запись, формат и арифметика
Пишем в ячейку настоящий datetime, показываем его по-русски маской DD.MM.YYYY и считаем дни между датами — а заодно узнаём, почему дату из файла нельзя сравнивать со строкой.
openpyxl дата в ячейкеexcel python дата
NumPy · Урок 3
Индексация и срезы массивов NumPy: как достать любой элемент
Индексы с нуля и с конца, срезы с шагом, строки и столбцы двумерных массивов — и главный подводный камень NumPy: срез возвращает view, а не копию. С примерами, разбором ошибок и шпаргалкой.
разница между copy и view в numpy
Datetime · Урок 6
timedelta: разница между моментами
«Срок подписки истекает через 7 дней», «событие было 3 часа назад» — за этим стоит timedelta. Вычитаем моменты, читаем дни и секунды, прибавляем разницу к датам.
timedelta pythonpython timedelta примеры