calendar: месяц и год в текст
Модуль calendar рисует месяц одной строкой, говорит, каким днём недели была дата, проверяет год на високосность и сообщает размер месяца — без единого объекта datetime.
Редакция Питоники
Часовые пояса из прошлого урока закрыли самый хитрый вопрос дат. Теперь — про самый наглядный: календарь как картинка. В отчёте, в консольной утилите, в ответе на вопрос «а сколько в октябре будней?» нужен не момент времени, а месяц целиком: сетка дней, дни недели, границы месяца. Для этого в стандартной библиотеке есть отдельный модуль — calendar.
calendar работает не с объектами, а с числами и словами: год, номер месяца, номер дня недели. В этом его сила — не нужно собирать datetime, чтобы узнать мелочь про дату.
calendar.month: месяц одной строкой
Главная функция модуля делает то, что руками заняло бы полчаса: печатает сетку месяца.
import calendar
print(calendar.month(2026, 10))
October 2026
Mo Tu We Th Fr Sa Su
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Разбираем картинку. Первая строка — заголовок с названием месяца и годом (названия по-английски — об этом ниже). Вторая — шапка дней недели Mo Tu We Th Fr Sa Su: неделя начинается с понедельника. Дальше — недели месяца строками: числа выровнены в колонки по два символа, а дни соседних месяцев заменены пробелами. 21 октября 2026 стоит под We — среда. И заметьте: после сетки в выводе проскочила пустая строка — причина у неё честная, см. врезку.
calendar.weekday: день недели по трём числам
import calendar
print(calendar.weekday(2026, 10, 21)) # среда
print(calendar.weekday(2026, 10, 26)) # понедельник
2 0
calendar.weekday(год, месяц, день) возвращает номер дня недели: 0 — понедельник, 6 — воскресенье. Это те же номера, что даёт метод d.weekday() из урока про дни недели, только объект datetime собирать не нужно — достаточно трёх чисел. 21 октября 2026 — номер 2, то есть среда; 26-е — номер 0, понедельник.
calendar.isleap: високосный год
Второй частый вопрос про календарь — високосный ли год. Проверять делением на 4 руками — классический источник ошибок, потому что правило длиннее:
import calendar
print(calendar.isleap(2028))
print(calendar.isleap(2026))
print(calendar.isleap(1900))
print(calendar.isleap(2000))
True False False True
Правило в три шага: год високосный, если делится на 4; но кратные 100 — обычные; и только кратные 400 — снова високосные. Поэтому 2028-й — високосный, 2026-й — нет, 1900-й — нет (делится на 100, но не на 400), а 2000-й — да. calendar.isleap(год) возвращает честный True или False и принимает только год — не месяц и не день.
calendar.monthrange: где начинается месяц и сколько в нём дней
Последняя функция закрывает сразу два вопроса: чем является первое число месяца и сколько дней в месяце вообще.
import calendar
print(calendar.monthrange(2026, 10))
print(calendar.monthrange(2026, 10)[1])
(calendar.THURSDAY, 31) 31
Кортеж из двух чисел: первый элемент — день недели, на который приходится 1-е число (октябрь 2026 начался с четверга), второй — сколько дней в месяце. Индекс [1] достаёт длину месяца — October, как и положено, 31 день. Если у вас более старый Python (до 3.12), первый элемент напечатается просто числом 3 — это тот же четверг: с 3.12 calendar возвращает именованные дни вроде THURSDAY, и число за ним то же самое.
| Вызов | Что вернёт | Для октября 2026 |
|---|---|---|
| calendar.month(2026, 10) | строка-сетка месяца | « October 2026 …» |
| calendar.weekday(2026, 10, 21) | номер дня недели, 0 — пн | 2 |
| calendar.isleap(2026) | True или False | False |
| calendar.monthrange(2026, 10)[1] | число дней в месяце | 31 |
| calendar.monthcalendar(2026, 10)[0] | первая неделя списком | [0, 0, 0, 1, 2, 3, 4] |
Что дальше
calendar отвечает на вопросы про месяц, но не считает жизненные промежутки: сколько человеку лет, сколько дней до экзамена. Это задачи следующего урока — возраст и разница в днях, где timedelta из урока про разницы превращается в конкретные числа. А если нужен месяц с русскими названиями — сетку calendar можно обрамить заголовком из словаря месяцев урока 9.
Сетка как данные, а не как картинка
До сих пор мы смотрели на месяц глазами: month() рисует картинку, и это удобно для отчёта или подсказки в консоли. Но как только месяц нужен для счёта, картинка не годится — её придётся парсить пробелами. И тут выручает вторая функция: monthcalendar возвращает не строку, а список списков по семь чисел, где чужие дни заменены нулями.
import calendar
weeks = calendar.monthcalendar(2026, 10)
print(len(weeks))
print(weeks[0])
print(weeks[-1])
5 [0, 0, 0, 1, 2, 3, 4] [26, 27, 28, 29, 30, 31, 0]
Пять недель — столько поместилось в октябрь 2026 года. Первая неделя начинается с нулей: 1 октября 2026 года приходится на четверг, а нули стоят в местах понедельника, вторника и среды. Последняя неделя заканчивается нулём в воскресенье, потому что 31 октября — пятница, и дальше месяца нет. Числа вместо дат — сознательное решение: нулем обозначен день, которого в этом месяце нет, и его легко отсечь проверкой if day.
import calendar
def workdays_in_month(year, month):
total = 0
for week in calendar.monthcalendar(year, month):
for day in week:
if day and calendar.weekday(year, month, day) < 5:
total += 1
return total
print(workdays_in_month(2026, 10))
print(workdays_in_month(2026, 2))
print(workdays_in_month(2026, 11))
22 20 21
В октябре 2026 года 22 будня, в феврале 20 — февраль короче на целые девять дней, из которых пять будних, а в ноябре 21: ноябрь начинается в воскресенье, поэтому первый рабочий день — понедельник 2-го, и месяц заканчивается в понедельник 30-го, то есть последняя неделя умещается целиком. Мы этим считал трудоёмкость спринта: сколько рабочих дней у команды до конца месяца. Отдельная функция с циклом из урока про рабочие дни даёт тот же ответ, но месяц — это готовый список, и пересчитывать его не нужно.
Месяц по-русски
Заголовок calendar.month всегда английский, и это не лень модуля, а его решение: модуль не зависит от настроек языка системы. Но месяц с русскими названиями получается сам, если взять сетку из monthcalendar, а шапку собрать вручную из словаря — того самого, что разбирали в уроке про русские даты.
import calendar
months = {10: 'Октябрь'}
weekdays = ['Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб', 'Вс']
print(months[10])
print(' '.join(weekdays))
for week in calendar.monthcalendar(2026, 10):
row = [f'{d:>2}' if d else ' ' for d in week]
print(' '.join(row).rstrip())
Октябрь
Пн Вт Ср Чт Пт Сб Вс
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31Заголовок «Октябрь» из словаря, шапка дней недели — список из двухбуквенных сокращений, а числа — шириной в два символа, чтобы колонки встали ровно. Куски дни соседних месяцев заменены двумя пробелами, а rstrip() в конце убирает хвостовые пробелы у последней строки, где месяц обрывается на 31-м числе. Две детали, из-за которых это перестаёт быть «просто напечатать»: календарь в ru-локали меняет формат дат, а список месяцев в Python короткий до «янв» — приходится разворачивать словарь вручную.
Глобальная настройка, о которой забывают
В модуле есть настройка первого дня недели — и она глобальная, то есть влияет на весь процесс, а не на конкретный вызов. Меняют её через setfirstweekday, и после этого меняется всё: и нарисованная сетка, и номера дней в ней, и monthcalendar.
import calendar
print(calendar.month(2026, 10).splitlines()[1])
calendar.setfirstweekday(calendar.SUNDAY)
print(calendar.month(2026, 10).splitlines()[1])
calendar.setfirstweekday(calendar.MONDAY)
print(calendar.month(2026, 10).splitlines()[1])
Mo Tu We Th Fr Sa Su Su Mo Tu We Th Fr Sa Mo Tu We Th Fr Sa Su
Заголовок переехал: неделя теперь начинается с воскресенья, и весь месяц сдвинулся на одну колонку. Третья строка возвращает всё как было — поэтому в рабочем коде мы всегда ставим setfirstweekday обратно, иначе следующий импорт из этого модуля получит неделю с другой нумерацией. Отдельно про цифры: setfirstweekday меняет и weekday(), то есть номера дней, которые раньше означали «понедельник», теперь означают «воскресенье». Код, который сравнивал weekday() с магическим числом, после такой смены молча начинал считать не то.
import calendar
cal = calendar.Calendar(firstweekday=calendar.SUNDAY)
print(cal.monthdayscalendar(2026, 10)[0])
print(calendar.month(2026, 10).splitlines()[1])
[0, 0, 0, 0, 1, 2, 3] Mo Tu We Th Fr Sa Su
Собственный экземпляр Calendar с нужным первым днём недели работает, а модуль остался нетронутым: вторая строка по-прежнему показывает привычную шапку с понедельника. В месячных данных появился ноль в воскресеньем месте — 1 октября 2026 года было в четверг, поэтому первая неделя теперь начинается с 4-го и в ней 1, 2, 3 октября. Для приложения, которое печатает календарь, — честный выход; для отчёта, который читают люди, мы всё равно оставляем международный порядок с понедельника.
Кортеж monthrange на разбор
monthrange отдаёт кортеж, и с ним удобно работать по частям. Отдельно стоит сказать про первый элемент: начиная с Python 3.12 это не число, а именованный день недели из перечисления — печать показывает calendar.THURSDAY вместо 3. Число то же самое, но код, который сравнивал его с литералом, пришлось бы поправить при обновлении Python.
import calendar
first, days = calendar.monthrange(2026, 10)
print(first)
print(days)
print(int(first))
3 31 3
Распаковка в две переменные работает и на 3.12, и на более ранних версиях, а int(first) возвращает обычное число в любом случае — так код переживает смену представления. Мы держим в проекте именно распаковку с int: она не сломается ни при переходе на новый Python, ни при возврате к старому, а разница между версиями — ровно то, из-за чего такие мелочи обычно и ломаются.
| Вызов | Что вернёт | Для октября 2026 |
|---|---|---|
| calendar.month(2026, 10) | строка-сетка месяца | « October 2026 …» |
| calendar.weekday(2026, 10, 21) | номер дня недели, 0 — пн | 2 |
| calendar.isleap(2026) | True или False | False |
| calendar.monthrange(2026, 10)[1] | число дней в месяце | 31 |
| calendar.monthcalendar(2026, 10)[0] | первая неделя списком | [0, 0, 0, 1, 2, 3, 4] |
| calendar.monthrange(2028, 2)[1] | дней в феврале високосного года | 29 |
| calendar.monthrange(2026, 2)[1] | дней в феврале обычного года | 28 |
Правило выбора между двумя способами ответа на один вопрос: если нужно нарисовать, берите month() — результат готов к печати. Если нужно посчитать, берите monthcalendar() — результат готов к циклу. Смешивать их не стоит: парсить пробелы из нарисованной сетки — это работа, которую модуль за вас уже сделал.
calendar умеет то, до чего объекты datetime не договариваются: нарисовать месяц целиком, как на бумажном календаре.
Сначала предскажи ответ в голове — это главный навык программиста.
import calendar
print(calendar.weekday(2028, 2, 29))
import calendar
print(calendar.isleap(2024) + calendar.isleap(2025))
import calendar
print(calendar.weekday(2026, 10, 25) < 5)
import calendar
weeks = calendar.monthcalendar(2026, 10)
print(len(weeks))
print(weeks[0])
import calendar
first, days = calendar.monthrange(2026, 10)
print(int(first))
print(days)
1. Что возвращает calendar.month(2026, 10)?
2. calendar.weekday(2026, 10, 21) вернул 2. Какой это день недели?
3. Почему calendar.isleap(1900) возвращает False?
4. Что лежит в кортеже, который возвращает calendar.monthrange(2026, 10)?
5. Почему print(calendar.month(2026, 10)) печатает пустую строку после сетки?
6. Что означают нули в списке, который возвращает calendar.monthcalendar?
7. Чем опасен вызов calendar.setfirstweekday(calendar.SUNDAY) в прикладном коде?
8. Что вернёт calendar.monthrange(2026, 10)[0] на Python 3.12 и новее?
9. Сколько рабочих дней было в октябре 2026 года по подсчёту через monthcalendar?
Соберите мини-справку по календарю тремя вызовами calendar: напечатайте сетку ноября 2026, выведите номер дня недели для 1 января 2027 и число дней в феврале 2028. Порядок вывода — именно такой.
Что делает модуль calendar в Python?
Это модуль стандартной библиотеки для календарных задач без объектов datetime: calendar.month печатает сетку месяца текстом, calendar.weekday возвращает номер дня недели по дате, calendar.isleap проверяет год на високосность, calendar.monthrange даёт день недели первого числа и длину месяца. Все функции принимают обычные числа.
Урок 15: calendar в PythonКак напечатать месяц календарём в консоли Python?
Одной строкой: import calendar, затем calendar.month(год, номер_месяца) — например calendar.month(2026, 10) вернёт сетку октября 2026 с неделями от понедельника. Чтобы напечатать сразу без лишней пустой строки, используйте calendar.prmonth(2026, 10) или print(calendar.month(2026, 10), end='').
Почему названия месяцев в calendar на английском?
calendar.month всегда рисует английский заголовок вроде October 2026: модуль не зависит от языковых настроек системы. Русское название над сеткой удобно собрать самому словарём месяцев и strftime-кодами из урока про русские даты, а саму сетку оставить как есть — числа в ней интернациональны.
Урок 9: русские датыС какого дня недели начинается неделя в calendar?
По умолчанию с понедельника: первый столбец сетки — Mo, а calendar.weekday считает понедельник нулём. Перенастроить можно вызовом calendar.setfirstweekday(calendar.SUNDAY) — тогда неделя начнётся с воскресенья и в сетке, и в monthcalendar.
Как посчитать рабочие дни в месяце через calendar?
Возьмите calendar.monthcalendar(год, месяц) и пройдите по неделям: нули пропускайте, а для каждого дня считайте calendar.weekday(год, месяц, день) < 5. В октябре 2026 года получится 22 будня, в феврале — 20, в ноябре — 21.
Урок 17: рабочие дниКак получить месяц с русскими названиями дней недели?
Сетку берите из calendar.monthcalendar(год, месяц) — это данные, а не картинка, — а шапку рисуйте сами из списка ['Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб', 'Вс'], а название месяца — из словаря месяцев урока про русские даты. Каждый день форматируйте шириной в два символа, чтобы колонки встали ровно.
Урок 9: русские датыПочему monthrange в Python 3.12 возвращает calendar.THURSDAY, а не число 3?
С 3.12 дни недели в модуле calendar стали элементами перечисления, и первый элемент кортежа monthrange — именованный день. Значение то же самое, поэтому int(first) даёт обычное число 3 и код переживает смену представления. Старый код, сравнивавший результат с литералом, при обновлении Python пришлось бы поправить.
Как нарисовать календарь в HTML?
В модуле есть класс HTMLCalendar: cal = calendar.HTMLCalendar(), затем cal.formatmonth(2026, 10) возвращает готовый фрагмент HTML-таблицы с классом month. Для простого текста хватает calendar.month, а для HTML этот класс экономит всю разметку вручную.
Понравился урок? Сошлитесь на него
«calendar умеет то, до чего объекты datetime не договариваются: нарисовать месяц целиком, как на бумажном календаре.»
Скопируйте готовую ссылку в формате HTML, Markdown или чистый адрес и вставьте в статью на Habr, VC, Telegram-канал или свой блог — так о проекте узнают новые читатели.
Что дальше
Осталось в разделе: 20 из 20. Прогресс хранится в вашем браузере.
Что читать дальше
Datetime · Урок 14
Часовые пояса: timezone и offset
Naive-дата не знает своего пояса, aware — знает: tzinfo хранит смещение от UTC. Один момент в двух поясах — разные цифры на часах и одно и то же время.
Datetime · Урок 16
Возраст и разница в днях
Полный возраст из даты рождения с учётом того, был ли день рождения в этом году, дни до события и недели из timedelta — три расчёта, которые нужны в любом проекте с датами.
Похожие уроки по темам
Подобраны автоматически по пересечению тем и ключевых слов.
Matplotlib / Seaborn · Урок 6
Оформление графиков: легенды, аннотации, сетки и стили Matplotlib
Легенда, аннотации со стрелками, сетка, готовые стили и rcParams — доводим график Matplotlib до презентационного уровня и сохраняем в PNG для слайдов.
сетка на графике matplotlib gridтекст на графике matplotlib
Строки · Урок 1
Строки в Python: текст как данные
Строка — самый частый тип данных в реальных задачах: имена, города, сообщения. Знакомимся с типом str: кавычки, len, конкатенация и умножение — и узнаём, почему строку нельзя переписать на месте.
Datetime · Урок 12
weekday и isoweekday: дни недели
weekday() считает с нуля: 0 — понедельник. isoweekday() — с единицы, по стандарту ISO-8601. Достаём день недели из даты, переводим в «среду» и находим ближайший понедельник.
weekday pythonpython день недели по дате